habarico 7.2.0.pre.beta.5

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 (221) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +82 -0
  4. data/Rakefile +15 -0
  5. data/VERSION +1 -0
  6. data/app/controllers/habarico/action_schema_controller.rb +41 -0
  7. data/app/controllers/habarico/application_controller.rb +15 -0
  8. data/app/controllers/habarico/contrib/preferences/user_preferences_controller.rb +105 -0
  9. data/app/controllers/habarico/global_schema_controller.rb +11 -0
  10. data/app/controllers/habarico/local_schema_controller.rb +35 -0
  11. data/app/helpers/habarico/application_helper.rb +4 -0
  12. data/app/models/habarico/application_record.rb +5 -0
  13. data/app/views/layouts/habarico/application.html.erb +17 -0
  14. data/config/locales/cs.yml +39 -0
  15. data/config/locales/de.yml +39 -0
  16. data/config/locales/en.yml +39 -0
  17. data/config/locales/es.yml +39 -0
  18. data/config/locales/fr.yml +39 -0
  19. data/config/locales/it.yml +39 -0
  20. data/config/locales/ja.yml +39 -0
  21. data/config/locales/nl.yml +39 -0
  22. data/config/locales/pl.yml +39 -0
  23. data/config/locales/pt.yml +39 -0
  24. data/config/locales/ro.yml +39 -0
  25. data/config/locales/sk.yml +39 -0
  26. data/config/locales/uk.yml +39 -0
  27. data/config/routes.rb +1 -0
  28. data/lib/generators/habarico/preferences/install_generator.rb +27 -0
  29. data/lib/generators/habarico/preferences/templates/create_habarico_user_preferences.rb.tt +15 -0
  30. data/lib/habarico/active_model/references.rb +68 -0
  31. data/lib/habarico/authenticatable.rb +31 -0
  32. data/lib/habarico/config.rb +71 -0
  33. data/lib/habarico/context.rb +126 -0
  34. data/lib/habarico/contrib/preferences/base_preferences.rb +40 -0
  35. data/lib/habarico/contrib/preferences/config.rb +31 -0
  36. data/lib/habarico/contrib/preferences/errors.rb +79 -0
  37. data/lib/habarico/contrib/preferences/locales/cs.yml +8 -0
  38. data/lib/habarico/contrib/preferences/locales/de.yml +8 -0
  39. data/lib/habarico/contrib/preferences/locales/en.yml +8 -0
  40. data/lib/habarico/contrib/preferences/locales/es.yml +8 -0
  41. data/lib/habarico/contrib/preferences/locales/fr.yml +8 -0
  42. data/lib/habarico/contrib/preferences/locales/it.yml +8 -0
  43. data/lib/habarico/contrib/preferences/locales/ja.yml +8 -0
  44. data/lib/habarico/contrib/preferences/locales/nl.yml +8 -0
  45. data/lib/habarico/contrib/preferences/locales/pl.yml +8 -0
  46. data/lib/habarico/contrib/preferences/locales/pt.yml +8 -0
  47. data/lib/habarico/contrib/preferences/locales/ro.yml +8 -0
  48. data/lib/habarico/contrib/preferences/locales/sk.yml +8 -0
  49. data/lib/habarico/contrib/preferences/locales/uk.yml +8 -0
  50. data/lib/habarico/contrib/preferences/registry.rb +293 -0
  51. data/lib/habarico/contrib/preferences/routes.rb +17 -0
  52. data/lib/habarico/contrib/preferences/schemas.rb +129 -0
  53. data/lib/habarico/contrib/preferences/serializer.rb +19 -0
  54. data/lib/habarico/contrib/preferences/user_preference.rb +16 -0
  55. data/lib/habarico/contrib/preferences.rb +32 -0
  56. data/lib/habarico/controller.rb +584 -0
  57. data/lib/habarico/definition/action_reference.rb +18 -0
  58. data/lib/habarico/definition/actions/action.rb +14 -0
  59. data/lib/habarico/definition/actions/create_action.rb +18 -0
  60. data/lib/habarico/definition/actions/delete_action.rb +20 -0
  61. data/lib/habarico/definition/actions/edit_action.rb +18 -0
  62. data/lib/habarico/definition/actions/link_action.rb +11 -0
  63. data/lib/habarico/definition/actions/resource_action.rb +12 -0
  64. data/lib/habarico/definition/actions/save_action.rb +14 -0
  65. data/lib/habarico/definition/actions/show_action.rb +18 -0
  66. data/lib/habarico/definition/actions/upload_images_action.rb +11 -0
  67. data/lib/habarico/definition/actions/view_action.rb +11 -0
  68. data/lib/habarico/definition/boxes/array_box.rb +10 -0
  69. data/lib/habarico/definition/boxes/box.rb +32 -0
  70. data/lib/habarico/definition/boxes/gallery_box.rb +10 -0
  71. data/lib/habarico/definition/boxes/grid_box.rb +10 -0
  72. data/lib/habarico/definition/boxes/grid_item_box.rb +10 -0
  73. data/lib/habarico/definition/boxes/list_box.rb +10 -0
  74. data/lib/habarico/definition/boxes/object_box.rb +10 -0
  75. data/lib/habarico/definition/boxes/tab_box.rb +10 -0
  76. data/lib/habarico/definition/boxes/tab_item_box.rb +10 -0
  77. data/lib/habarico/definition/boxes/wizard_box.rb +10 -0
  78. data/lib/habarico/definition/concerns/definition_model.rb +38 -0
  79. data/lib/habarico/definition/definition.rb +206 -0
  80. data/lib/habarico/definition/field_reference.rb +9 -0
  81. data/lib/habarico/definition/filter_reference.rb +7 -0
  82. data/lib/habarico/definition/menu/link_menu_item.rb +12 -0
  83. data/lib/habarico/definition/menu/menu_item.rb +9 -0
  84. data/lib/habarico/definition/menu/text_menu_item.rb +12 -0
  85. data/lib/habarico/definition/menu/view_menu_item.rb +13 -0
  86. data/lib/habarico/definition/response.rb +13 -0
  87. data/lib/habarico/definition/views/action_view.rb +11 -0
  88. data/lib/habarico/definition/views/create_view.rb +15 -0
  89. data/lib/habarico/definition/views/edit_view.rb +15 -0
  90. data/lib/habarico/definition/views/list_view.rb +17 -0
  91. data/lib/habarico/definition/views/show_view.rb +15 -0
  92. data/lib/habarico/definition/views/view.rb +10 -0
  93. data/lib/habarico/engine.rb +6 -0
  94. data/lib/habarico/file_validations.rb +119 -0
  95. data/lib/habarico/generators/abstract_generator.rb +21 -0
  96. data/lib/habarico/generators/action/action_schema_generator.rb +111 -0
  97. data/lib/habarico/generators/action/confirm_dialog_properties_generator.rb +30 -0
  98. data/lib/habarico/generators/action/global_resource_action_schema_generator.rb +46 -0
  99. data/lib/habarico/generators/action/link_action_parameters_generator.rb +31 -0
  100. data/lib/habarico/generators/action/resource_action_endpoint_parameters_generator.rb +70 -0
  101. data/lib/habarico/generators/action/resource_action_parameters_generator.rb +40 -0
  102. data/lib/habarico/generators/action/resource_action_schema_generator.rb +51 -0
  103. data/lib/habarico/generators/action/save_action_parameters_generator.rb +26 -0
  104. data/lib/habarico/generators/action/upload_images_action_parameters_generator.rb +41 -0
  105. data/lib/habarico/generators/action/view_action_endpoint_parameters_generator.rb +37 -0
  106. data/lib/habarico/generators/action/view_action_parameters_generator.rb +56 -0
  107. data/lib/habarico/generators/endpoint/dynamic_render_options_generator.rb +26 -0
  108. data/lib/habarico/generators/endpoint/endpoints_schema_generator.rb +61 -0
  109. data/lib/habarico/generators/endpoint/enum_type_generator.rb +45 -0
  110. data/lib/habarico/generators/endpoint/field_options_generator.rb +179 -0
  111. data/lib/habarico/generators/endpoint/field_schema_generator.rb +381 -0
  112. data/lib/habarico/generators/endpoint/fields_schema_generator.rb +35 -0
  113. data/lib/habarico/generators/endpoint/filter_schema_generator.rb +201 -0
  114. data/lib/habarico/generators/endpoint/filters_schema_generator.rb +25 -0
  115. data/lib/habarico/generators/endpoint/method_schema_generator.rb +34 -0
  116. data/lib/habarico/generators/endpoint/number_field_options_generator.rb +45 -0
  117. data/lib/habarico/generators/endpoint/phone_field_options_generator.rb +29 -0
  118. data/lib/habarico/generators/endpoint/relation_field_options_generator.rb +90 -0
  119. data/lib/habarico/generators/endpoint/search_filter_schema_generator.rb +44 -0
  120. data/lib/habarico/generators/menu/menu_group_schema_generator.rb +63 -0
  121. data/lib/habarico/generators/menu/menu_link_parameters_generator.rb +31 -0
  122. data/lib/habarico/generators/menu/menu_schema_generator.rb +105 -0
  123. data/lib/habarico/generators/menu/menu_view_endpoint_parameters_generator.rb +44 -0
  124. data/lib/habarico/generators/menu/menu_view_parameters_generator.rb +50 -0
  125. data/lib/habarico/generators/resource/action_resource_schema_generator.rb +84 -0
  126. data/lib/habarico/generators/resource/global_resource_schema_generator.rb +70 -0
  127. data/lib/habarico/generators/resource/local_resource_schema_generator.rb +111 -0
  128. data/lib/habarico/generators/resource/object_resource_schema_generator.rb +49 -0
  129. data/lib/habarico/generators/resource/resource_schema_generator.rb +31 -0
  130. data/lib/habarico/generators/view/action_descriptor_generator.rb +88 -0
  131. data/lib/habarico/generators/view/array_box_parameters_generator.rb +34 -0
  132. data/lib/habarico/generators/view/box_options_generator.rb +23 -0
  133. data/lib/habarico/generators/view/box_schema_generator.rb +194 -0
  134. data/lib/habarico/generators/view/detail_view_parameters_generator.rb +57 -0
  135. data/lib/habarico/generators/view/field_descriptor_generator.rb +107 -0
  136. data/lib/habarico/generators/view/filter_descriptor_generator.rb +75 -0
  137. data/lib/habarico/generators/view/gallery_box_parameters_generator.rb +71 -0
  138. data/lib/habarico/generators/view/global_view_schema_generator.rb +59 -0
  139. data/lib/habarico/generators/view/grid_item_box_parameters_generator.rb +44 -0
  140. data/lib/habarico/generators/view/list_box_parameters_generator.rb +124 -0
  141. data/lib/habarico/generators/view/list_view_parameters_generator.rb +120 -0
  142. data/lib/habarico/generators/view/object_box_parameters_generator.rb +26 -0
  143. data/lib/habarico/generators/view/sort_descriptor_generator.rb +30 -0
  144. data/lib/habarico/generators/view/tab_box_parameters_generator.rb +26 -0
  145. data/lib/habarico/generators/view/view_endpoint_parameters_generator.rb +71 -0
  146. data/lib/habarico/generators/view/view_schema_generator.rb +87 -0
  147. data/lib/habarico/generators/view/wizard_box_parameters_generator.rb +32 -0
  148. data/lib/habarico/helpers/action_helper.rb +9 -0
  149. data/lib/habarico/helpers/attributes_helper.rb +67 -0
  150. data/lib/habarico/helpers/box_helper.rb +19 -0
  151. data/lib/habarico/helpers/callable_helper.rb +15 -0
  152. data/lib/habarico/helpers/filter_params_helper.rb +49 -0
  153. data/lib/habarico/helpers/order_by_helper.rb +56 -0
  154. data/lib/habarico/helpers/reflection_helper.rb +61 -0
  155. data/lib/habarico/helpers/sorting_helper.rb +136 -0
  156. data/lib/habarico/schema/action/action_schema.rb +46 -0
  157. data/lib/habarico/schema/action/confirm_dialog_properties.rb +13 -0
  158. data/lib/habarico/schema/action/global_resource_action_schema.rb +29 -0
  159. data/lib/habarico/schema/action/link_action_parameters.rb +15 -0
  160. data/lib/habarico/schema/action/resource_action_endpoint_parameters.rb +24 -0
  161. data/lib/habarico/schema/action/resource_action_parameters.rb +17 -0
  162. data/lib/habarico/schema/action/resource_action_schema.rb +14 -0
  163. data/lib/habarico/schema/action/save_action_parameters.rb +10 -0
  164. data/lib/habarico/schema/action/upload_images_action_parameters.rb +23 -0
  165. data/lib/habarico/schema/action/view_action_endpoint_parameters.rb +16 -0
  166. data/lib/habarico/schema/action/view_action_parameters.rb +27 -0
  167. data/lib/habarico/schema/endpoint/ai_field_options.rb +10 -0
  168. data/lib/habarico/schema/endpoint/box_options.rb +10 -0
  169. data/lib/habarico/schema/endpoint/choices_field_options.rb +10 -0
  170. data/lib/habarico/schema/endpoint/dynamic_render_options.rb +14 -0
  171. data/lib/habarico/schema/endpoint/endpoints_schema.rb +22 -0
  172. data/lib/habarico/schema/endpoint/enum_type.rb +21 -0
  173. data/lib/habarico/schema/endpoint/field_options.rb +56 -0
  174. data/lib/habarico/schema/endpoint/field_schema.rb +65 -0
  175. data/lib/habarico/schema/endpoint/filter_schema.rb +44 -0
  176. data/lib/habarico/schema/endpoint/image_field_options.rb +13 -0
  177. data/lib/habarico/schema/endpoint/key_value.rb +13 -0
  178. data/lib/habarico/schema/endpoint/method_schema.rb +13 -0
  179. data/lib/habarico/schema/endpoint/number_field_options.rb +24 -0
  180. data/lib/habarico/schema/endpoint/phone_field_options.rb +13 -0
  181. data/lib/habarico/schema/endpoint/relation_field_options.rb +39 -0
  182. data/lib/habarico/schema/extensible_enum.rb +20 -0
  183. data/lib/habarico/schema/menu/action_view_choice.rb +13 -0
  184. data/lib/habarico/schema/menu/menu_group_schema.rb +26 -0
  185. data/lib/habarico/schema/menu/menu_schema.rb +36 -0
  186. data/lib/habarico/schema/resource/global_resource_schema.rb +14 -0
  187. data/lib/habarico/schema/resource/local_resource_schema.rb +14 -0
  188. data/lib/habarico/schema/resource/object_resource_schema.rb +8 -0
  189. data/lib/habarico/schema/resource/resource_schema.rb +16 -0
  190. data/lib/habarico/schema/types.rb +102 -0
  191. data/lib/habarico/schema/view/action_descriptor.rb +30 -0
  192. data/lib/habarico/schema/view/array_box_parameters.rb +13 -0
  193. data/lib/habarico/schema/view/box_schema.rb +67 -0
  194. data/lib/habarico/schema/view/detail_view_parameters.rb +16 -0
  195. data/lib/habarico/schema/view/field_descriptor.rb +50 -0
  196. data/lib/habarico/schema/view/field_wrapper_properties.rb +13 -0
  197. data/lib/habarico/schema/view/filter_descriptor.rb +24 -0
  198. data/lib/habarico/schema/view/gallery_box_parameters.rb +31 -0
  199. data/lib/habarico/schema/view/global_view_schema.rb +25 -0
  200. data/lib/habarico/schema/view/grid_item_box_parameters.rb +13 -0
  201. data/lib/habarico/schema/view/list_box_parameters.rb +44 -0
  202. data/lib/habarico/schema/view/list_view_parameters.rb +31 -0
  203. data/lib/habarico/schema/view/object_box_parameters.rb +10 -0
  204. data/lib/habarico/schema/view/sort_descriptor.rb +15 -0
  205. data/lib/habarico/schema/view/sortable_parameters.rb +13 -0
  206. data/lib/habarico/schema/view/tab_box_parameters.rb +10 -0
  207. data/lib/habarico/schema/view/view_action_descriptor_parameters.rb +21 -0
  208. data/lib/habarico/schema/view/view_endpoint_parameters.rb +22 -0
  209. data/lib/habarico/schema/view/view_schema.rb +33 -0
  210. data/lib/habarico/schema/view/wizard_box_parameters.rb +13 -0
  211. data/lib/habarico/search/field_resolver.rb +138 -0
  212. data/lib/habarico/search/search_matcher.rb +152 -0
  213. data/lib/habarico/serializers/meta_serializer.rb +42 -0
  214. data/lib/habarico/serializers/record_serializer.rb +33 -0
  215. data/lib/habarico/serializers/serializer.rb +61 -0
  216. data/lib/habarico/services/resource_service.rb +69 -0
  217. data/lib/habarico/services/routing_service.rb +160 -0
  218. data/lib/habarico/services/schema_service.rb +72 -0
  219. data/lib/habarico/version.rb +5 -0
  220. data/lib/habarico.rb +262 -0
  221. metadata +324 -0
@@ -0,0 +1,71 @@
1
+ module Habarico
2
+ class ViewEndpointParametersGenerator < AbstractGenerator
3
+
4
+ def generate(view, context)
5
+ # Validate arguments
6
+ raise Eluvia::Errors::StandardError.new('Argument `view` must be a Habarico::View') unless view.is_a?(Habarico::View)
7
+
8
+ # Create model
9
+ Habarico::ViewEndpointParameters.new(
10
+ path: generate_path(view, context),
11
+ method: generate_method(view, context),
12
+ reference: generate_reference(view, context),
13
+ action_name: generate_action_name(view, context)
14
+ )
15
+ end
16
+
17
+ protected
18
+
19
+ # ***********************************************************************
20
+ # Attribute generators
21
+ # ***********************************************************************
22
+
23
+ def generate_path(view, context)
24
+ # Standard views (list, show, create, edit) are served from the standard resource path, which the
25
+ # frontend derives on its own; only action views need an explicit path.
26
+ return nil if view.endpoint_action_name.blank?
27
+
28
+ url_prefix = Habarico::Config.routing_url_prefix.trim('/')
29
+ result = !url_prefix.blank? ? "/#{url_prefix}" : ''
30
+
31
+ # Add resource path
32
+ reference = generate_reference(view, context)
33
+ if reference.present?
34
+ resource_path_2 = ResourceService.instance.get_resource_path(reference)
35
+ result += "/#{resource_path_2.join('/')}"
36
+ else
37
+ result += "/#{resource_path.join('/')}"
38
+ end
39
+
40
+ # Add record PK
41
+ record_pk = generate_record_pk(view, context)
42
+ result += "/#{record_pk}" if record_pk
43
+
44
+ # Add action name
45
+ action_name = generate_action_name(view, context)
46
+ result += "/#{action_name}" if action_name
47
+
48
+ # Return result
49
+ "#{result}/"
50
+ end
51
+
52
+ def generate_method(view, _context)
53
+ view.endpoint_method
54
+ end
55
+
56
+ def generate_reference(view, _context)
57
+ view.endpoint_reference
58
+ end
59
+
60
+ def generate_action_name(view, _context)
61
+ view.endpoint_action_name
62
+ end
63
+
64
+ def generate_record_pk(view, context)
65
+ if view.is_detail
66
+ context.record_pk
67
+ end
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,87 @@
1
+ module Habarico
2
+ class ViewSchemaGenerator < AbstractGenerator
3
+
4
+ def generate(view, context)
5
+ # Validate arguments
6
+ raise Eluvia::Errors::StandardError.new('Argument `view` must be a Habarico::View') unless view.is_a?(Habarico::View)
7
+
8
+ # Create model
9
+ Habarico::ViewSchema.new(
10
+ type: generate_type(view, context),
11
+ name: generate_name(view, context),
12
+ label: generate_label(view, context),
13
+ is_detail: generate_is_detail(view, context),
14
+ list: generate_list(view, context),
15
+ detail: generate_detail(view, context),
16
+ endpoint: generate_endpoint(view, context),
17
+ breadcrumbs: generate_breadcrumbs(view, context),
18
+ )
19
+ end
20
+
21
+ protected
22
+
23
+ def list_view_parameters_generator
24
+ @list_view_parameters_generator ||= definition.list_view_parameters_generator.new(definition, resource_path)
25
+ end
26
+
27
+ def detail_view_parameters_generator
28
+ @detail_view_parameters_generator ||= definition.detail_view_parameters_generator.new(definition, resource_path)
29
+ end
30
+
31
+ def view_endpoint_parameters_generator
32
+ @view_endpoint_parameters_generator ||= definition.view_endpoint_parameters_generator.new(definition, resource_path)
33
+ end
34
+
35
+ def menu_schema_generator
36
+ @menu_schema_generator ||= definition.menu_schema_generator.new(definition, resource_path)
37
+ end
38
+
39
+ # ***********************************************************************
40
+ # Attribute generators
41
+ # ***********************************************************************
42
+
43
+ def generate_type(view, _context)
44
+ view.type
45
+ end
46
+
47
+ def generate_name(view, _context)
48
+ view.name
49
+ end
50
+
51
+ def generate_label(view, context)
52
+ view_name = generate_name(view, context).to_s
53
+ view.label ||
54
+ I18n.t("views.#{resource_path.join('.')}.#{view_name}", default: nil) ||
55
+ I18n.t("views.common.#{view_name}", default: nil)
56
+ end
57
+
58
+ def generate_is_detail(view, _context)
59
+ !!view.is_detail
60
+ end
61
+
62
+ def generate_list(view, context)
63
+ if view.type == :list
64
+ list_view_parameters_generator.generate(view, context)
65
+ end
66
+ end
67
+
68
+ def generate_detail(view, context)
69
+ unless view.type == :list
70
+ detail_view_parameters_generator.generate(view, context)
71
+ end
72
+ end
73
+
74
+ def generate_endpoint(view, context)
75
+ view_endpoint_parameters_generator.generate(view, context)
76
+ end
77
+
78
+ def generate_breadcrumbs(view, context)
79
+ if definition.breadcrumbs&.respond_to?(:call)
80
+ definition.breadcrumbs.call(view, context).map do |menu_item|
81
+ menu_schema_generator.generate(menu_item, nil)
82
+ end
83
+ end
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,32 @@
1
+ module Habarico
2
+ class WizardBoxParametersGenerator < AbstractGenerator
3
+
4
+ def generate(box, context)
5
+ # Validate arguments
6
+ raise Eluvia::Errors::StandardError.new('Argument `box` must be a Habarico::WizardBox') unless box.is_a?(Habarico::WizardBox)
7
+
8
+ # Create model
9
+ Habarico::WizardBoxParameters.new(
10
+ show_toolbar_navigation: generate_show_toolbar_navigation(box, context),
11
+ validate_steps: generate_validate_steps(box, context),
12
+ )
13
+ end
14
+
15
+ protected
16
+
17
+ # ***********************************************************************
18
+ # Attribute generators
19
+ # ***********************************************************************
20
+
21
+ # Defaults to true for wizard boxes.
22
+ def generate_show_toolbar_navigation(box, _context)
23
+ box.show_toolbar_navigation.nil? || !!box.show_toolbar_navigation
24
+ end
25
+
26
+ # Defaults to true.
27
+ def generate_validate_steps(box, _context)
28
+ box.validate_steps.nil? || !!box.validate_steps
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,9 @@
1
+ module Habarico
2
+ module ActionHelper
3
+
4
+ def self.is_standard_action?(action)
5
+ %w[show create update delete].include?(action.name)
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,67 @@
1
+ module Habarico
2
+ module AttributesHelper
3
+
4
+ # Extracts the underlying attribute name from a field entry as it may appear in `fields` /
5
+ # `read_only_fields`: a Symbol/String, a Hash (`{ name => subfields }`) or a FieldReference.
6
+ def self.field_name(field)
7
+ case field
8
+ when Habarico::FieldReference then field.name.to_s
9
+ when ::Hash then field.keys.first.to_s
10
+ else field.to_s
11
+ end
12
+ end
13
+
14
+ # FieldSchema attributes a FieldReference (used in `fields` / `read_only_fields`) may override.
15
+ FIELD_REFERENCE_OVERRIDES = %i[format label short_label help_text read_only write_only sortable sort_by default_value].freeze
16
+
17
+ # Applies a FieldReference's explicitly set attributes onto a generated FieldSchema, overriding
18
+ # the model-derived defaults. A `nil` FieldReference (Symbol/String/Hash field) is a no-op.
19
+ def self.apply_field_reference_overrides(schema, field_reference)
20
+ return schema if field_reference.nil?
21
+
22
+ FIELD_REFERENCE_OVERRIDES.each do |attr|
23
+ value = field_reference.public_send(attr)
24
+ schema.public_send("#{attr}=", value) unless value.nil?
25
+ end
26
+ schema
27
+ end
28
+
29
+ def self.is_amount_major_attr?(model, attr_name)
30
+ attr_name.to_s.end_with?('_major') && model.respond_to?(:has_amount_attr?) && model.has_amount_attr?(attr_name.to_s.sub(/_major\z/, ''))
31
+ end
32
+
33
+ def self.is_amount_minor_attr?(model, attr_name)
34
+ attr_name.to_s.end_with?('_minor') && model.respond_to?(:has_amount_attr?) && model.has_amount_attr?(attr_name.to_s.sub(/_minor\z/, ''))
35
+ end
36
+
37
+ def self.is_enum_obj_attr?(model, attr_name)
38
+ attr_name.to_s.end_with?('_obj') && model.respond_to?(:has_enum_attr?) && model.has_enum_attr?(attr_name.to_s.sub(/_obj\z/, ''))
39
+ end
40
+
41
+ def self.generate_attr_variants(model, attr_name)
42
+ attr_name = attr_name.to_s
43
+
44
+ # Amount
45
+ if is_amount_major_attr?(model, attr_name)
46
+ return [attr_name.sub(/_major\z/, ''), attr_name, attr_name.sub(/_major\z/, '_minor')]
47
+ end
48
+ if is_amount_minor_attr?(model, attr_name)
49
+ return [attr_name.sub(/_minor\z/, ''), attr_name, attr_name.sub(/_minor\z/, '_major')]
50
+ end
51
+ if model.respond_to?(:has_amount_attr?) && model.has_amount_attr?(attr_name)
52
+ return [attr_name, "#{attr_name}_major", "#{attr_name}_minor"]
53
+ end
54
+
55
+ # Enum
56
+ if is_enum_obj_attr?(model, attr_name)
57
+ return [attr_name.sub(/_obj\z/, ''), attr_name]
58
+ end
59
+ if model.respond_to?(:has_enum_attr?) && model.has_enum_attr?(attr_name)
60
+ return [attr_name, "#{attr_name}_obj"]
61
+ end
62
+
63
+ [attr_name]
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,19 @@
1
+ module Habarico
2
+ module BoxHelper
3
+ def self.get_referenced_definition(box)
4
+ if box.reference
5
+ return Habarico::ResourceService.instance.get_definition(Habarico::ResourceService.instance.get_resource_path(box.reference))
6
+ end
7
+ nil
8
+ end
9
+
10
+ def self.get_referenced_model(box)
11
+ referenced_definition = get_referenced_definition(box)
12
+ if referenced_definition
13
+ return referenced_definition.model
14
+ end
15
+ nil
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ module Habarico
2
+ module CallableHelper
3
+
4
+ def self.resolve(callable_or_value, context)
5
+ if callable_or_value.respond_to?(:call)
6
+ callable_or_value.call(context)
7
+ elsif callable_or_value.is_a?(::String)
8
+ callable_or_value.to_s.sub(/{id}/, context.record_pk.to_s)
9
+ else
10
+ callable_or_value
11
+ end
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,49 @@
1
+ module Habarico
2
+ # Parsing of the list-endpoint filter params. A named term can arrive either as a direct param
3
+ # (`{ search: 'x' }`) or embedded in the `:filter` string as a `&`-separated `field=<json>`
4
+ # term (`{ filter: 'search="x"&status="y"' }`). These helpers read or remove a single named term
5
+ # from either form without disturbing the others; the grammar mirrors the base library
6
+ # `parse_filter_term`, but stays lenient (never raises) since it only pre-processes params.
7
+ module FilterParamsHelper
8
+
9
+ # The decoded value of `key`, from the direct param or the `:filter` string; nil when absent or
10
+ # blank. Filter-string values are JSON-encoded (`search="x"`), so quotes are stripped; a value
11
+ # that is not valid JSON falls back to its raw text.
12
+ def self.extract(filter_params, key)
13
+ key = key.to_s
14
+ direct = filter_params[key.to_sym]
15
+ return direct.to_s if direct.present?
16
+
17
+ filter_string = filter_params[:filter]
18
+ return nil if filter_string.blank?
19
+
20
+ term = filter_string.to_s.split('&').find { |t| term_field(t) == key }
21
+ return nil if term.nil?
22
+
23
+ parse_value(term.split('=', 2)[1].to_s.strip).to_s.presence
24
+ end
25
+
26
+ # `filter_params` with `key` removed from both the direct param and the `:filter` string (the
27
+ # `:filter` key is dropped entirely when no other term remains). Other terms are left verbatim.
28
+ def self.strip(filter_params, key)
29
+ key = key.to_s
30
+ result = filter_params.except(key.to_sym)
31
+ return result if result[:filter].blank?
32
+
33
+ remaining = result[:filter].to_s.split('&').reject { |t| term_field(t) == key }
34
+ remaining.empty? ? result.except(:filter) : result.merge(filter: remaining.join('&'))
35
+ end
36
+
37
+ # The field name of a `field=<json>` term.
38
+ def self.term_field(term)
39
+ term.to_s.split('=', 2).first.to_s.strip
40
+ end
41
+
42
+ # JSON-decodes a raw filter value, falling back to the raw string when it is not valid JSON.
43
+ def self.parse_value(raw)
44
+ JSON.parse(raw)
45
+ rescue JSON::ParserError, TypeError
46
+ raw
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,56 @@
1
+ module Habarico
2
+ # The one place that reads the `field:direction` notation, shared by the `order_by` query
3
+ # parameter and the `default_sorting` declarations.
4
+ #
5
+ # `Eluvia::PaginationHandler#set_pagination` reads the query parameter exactly this way and
6
+ # answers 422 for anything that does not match, so by the time habarico looks at the parameter
7
+ # every entry is known to be well formed. This helper has to stay in step with it.
8
+ module OrderByHelper
9
+
10
+ DIRECTIONS = %w[asc desc].freeze
11
+
12
+ # Splits a whole `order_by` value (`name:asc,created_at:desc`) into `[field_name, direction]`
13
+ # pairs, dropping every entry that is not in that form - `set_pagination` has already refused
14
+ # the request over those, so nothing downstream has to decide what to do with them.
15
+ def self.parse(value)
16
+ value.to_s.split(',').filter_map { |entry| parse_entry(entry) }
17
+ end
18
+
19
+ # Splits a single `field:direction` entry, or nil when it is not exactly one non-blank field
20
+ # name followed by one known direction.
21
+ def self.parse_entry(entry)
22
+ # `-1` keeps a trailing empty segment (`name:`), so it is read as a missing direction
23
+ # instead of a bare field name - the same way `set_pagination` reads it.
24
+ parts = segments(entry)
25
+ return nil unless parts.length == 2
26
+
27
+ field_name, direction = parts
28
+ return nil if field_name.blank? || !DIRECTIONS.include?(direction)
29
+
30
+ [field_name, direction]
31
+ end
32
+
33
+ # Field names of a whole value, tolerating an entry written without a direction the way a
34
+ # hand-written `default_sorting` may be - this reads declarations, not requests.
35
+ def self.field_names(value)
36
+ value.to_s.split(',').filter_map { |entry| field_name(entry) }
37
+ end
38
+
39
+ # Field name of a single entry, tolerating a bare `field` without a direction: a
40
+ # `default_sorting` declaration is written by hand and may be spelled that way.
41
+ def self.field_name(entry)
42
+ segments(entry).first.presence
43
+ end
44
+
45
+ # Direction of a single entry - `asc` unless `desc` is spelled out.
46
+ def self.direction(entry)
47
+ segments(entry)[1] == 'desc' ? 'desc' : 'asc'
48
+ end
49
+
50
+ def self.segments(entry)
51
+ entry.to_s.strip.split(':', -1)
52
+ end
53
+ private_class_method :segments
54
+
55
+ end
56
+ end
@@ -0,0 +1,61 @@
1
+ module Habarico
2
+ module ReflectionHelper
3
+
4
+ # *************************************************************************
5
+ # Attributes model reflection methods
6
+ # *************************************************************************
7
+
8
+ def self.is_attributes_model_class?(model_class)
9
+ model_class.respond_to?(:ancestors) && model_class.ancestors.include?(Eluvia::ActiveModel::Attributes)
10
+ end
11
+
12
+ def self.is_attributes_model_instance?(model_instance)
13
+ model_instance.is_a?(Eluvia::ActiveModel::Attributes)
14
+ end
15
+
16
+ def self.is_has_one_reference?(model_class, field_name)
17
+ model_class.respond_to?(:has_reference?) && model_class.has_reference?(field_name, type: :has_one)
18
+ end
19
+
20
+ def self.is_has_many_reference?(model_class, field_name)
21
+ model_class.respond_to?(:has_reference?) && model_class.has_reference?(field_name, type: :has_many)
22
+ end
23
+
24
+ # *************************************************************************
25
+ # Active model reflection methods
26
+ # *************************************************************************
27
+
28
+ def self.is_active_model_class?(model_class)
29
+ model_class.respond_to?(:ancestors) && model_class.ancestors.include?(::ActiveModel::Model)
30
+ end
31
+
32
+ def self.is_active_model_instance?(model_instance)
33
+ model_instance.is_a?(::ActiveModel::Model)
34
+ end
35
+
36
+ # *************************************************************************
37
+ # Active record reflection methods
38
+ # *************************************************************************
39
+
40
+ def self.is_active_record_class?(model_class)
41
+ model_class.respond_to?(:ancestors) && model_class.ancestors.include?(::ActiveRecord::Base)
42
+ end
43
+
44
+ def self.is_active_record_instance?(model_instance)
45
+ model_instance.is_a?(::ActiveRecord::Base)
46
+ end
47
+
48
+ def self.is_belongs_to_association?(model_class, field_name)
49
+ is_active_record_class?(model_class) && model_class.reflect_on_association(field_name.to_sym)&.macro == :belongs_to
50
+ end
51
+
52
+ def self.is_has_one_association?(model_class, field_name)
53
+ is_active_record_class?(model_class) && model_class.reflect_on_association(field_name.to_sym)&.macro == :has_one
54
+ end
55
+
56
+ def self.is_has_many_association?(model_class, field_name)
57
+ is_active_record_class?(model_class) && model_class.reflect_on_association(field_name.to_sym)&.macro == :has_many
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,136 @@
1
+ module Habarico
2
+ # The one definition of what a resource offers for sorting.
3
+ #
4
+ # A field schema's `sort_by` is the value a client puts in `order_by` to sort by that field, so
5
+ # the schema generators and the `order_by` validator have to agree on it. They used to decide
6
+ # separately, and the validator's own rule - a declared field that is also a column - refused
7
+ # part of what the generators advertise: the lower bound of a range field, a column reached over
8
+ # an association, and a `sort_by` written on a `FieldReference` by hand.
9
+ module SortingHelper
10
+
11
+ # `sort_by` of a field of the resource schema (`FieldSchemaGenerator`): the field itself when a
12
+ # column or an attribute backs it, the lower bound when a range attribute does, and nothing
13
+ # when the field is not sortable on its own. `attr_name` is the attribute the field is backed
14
+ # by, which only differs from the field name where no range attribute can exist anyway.
15
+ def self.schema_sort_by(definition, model, field_name, attr_name: field_name)
16
+ if definition.sorting_fields.present?
17
+ return definition.sorting_fields.map(&:to_s).include?(field_name.to_s) ? field_name.to_s : nil
18
+ end
19
+
20
+ return nil unless Habarico::ReflectionHelper.is_active_record_class?(model)
21
+
22
+ # A standard DB column or a standard attribute of the model sorts under its own name.
23
+ Habarico::AttributesHelper.generate_attr_variants(model, field_name).each do |field_name_variant|
24
+ if model.columns_hash[field_name_variant.to_s] || model.attribute_names.include?(field_name_variant.to_s)
25
+ return field_name_variant.to_s
26
+ end
27
+ end
28
+
29
+ # A range attribute sorts by its lower bound, as long as that one is a DB column.
30
+ if model.respond_to?(:has_range_attr?) && model.has_range_attr?(attr_name)
31
+ lower_attr_name = model.find_range_attr(attr_name)[:lower_attr_name]
32
+ return lower_attr_name if model.columns_hash[lower_attr_name.to_s]
33
+ end
34
+
35
+ # Everything else is not sortable by default. To sort by a "belongs to" association, declare
36
+ # `sort_by` on the field descriptor.
37
+ nil
38
+ end
39
+
40
+ # `sort_by` of a field descriptor of a view (`FieldDescriptorGenerator`): a column of an
41
+ # association, addressed as `association__column` the way `order_2` expects it, or whatever the
42
+ # FieldReference declares for itself.
43
+ def self.descriptor_sort_by(model, field_reference)
44
+ name = field_reference.name.to_s
45
+ if Habarico::ReflectionHelper.is_active_record_class?(model) && name.include?('__')
46
+ split_name = name.split('__')
47
+ if split_name.size == 2
48
+ reflection = model.reflect_on_association(split_name[0].to_sym)
49
+ return name if reflection && reflection.klass.columns_hash[split_name[1]]
50
+ end
51
+ end
52
+
53
+ field_reference.sort_by
54
+ end
55
+
56
+ # Every value this resource accepts in `order_by`, or nil when it has no whitelist to offer -
57
+ # a model without ActiveRecord reflection exposes no columns to check a field name against, so
58
+ # the field names are left to the model itself.
59
+ def self.allowed_order_by_fields(definition)
60
+ # What the resource tells the frontend to sort by. The list view schema publishes a
61
+ # `default_sorting` - the resource's, or the view's own - and the frontend puts exactly that
62
+ # in `order_by` on the first request, so refusing it would refuse the backend's own answer.
63
+ declared = declared_sorting_fields(definition)
64
+
65
+ return (declared + definition.sorting_fields.map(&:to_s)).uniq if definition.sorting_fields.present?
66
+
67
+ model = definition.model
68
+ return nil unless Habarico::ReflectionHelper.is_active_record_class?(model)
69
+
70
+ names = declared
71
+
72
+ # What the resource schema advertises for each declared field.
73
+ (definition.fields + definition.read_only_fields).each do |field|
74
+ field_name = Habarico::AttributesHelper.field_name(field)
75
+ names << field.sort_by if field.is_a?(Habarico::FieldReference)
76
+ names << schema_sort_by(definition, model, field_name)
77
+ end
78
+
79
+ # What the resource's own list views add on top: a column of an association is declared
80
+ # there and never appears among the resource fields.
81
+ list_view_field_references(definition).each do |field_reference|
82
+ names << descriptor_sort_by(model, field_reference)
83
+ end
84
+
85
+ # Virtual sortable columns registered on the model with `order_attr`.
86
+ names += model.order_attrs.map { |order_attr| order_attr[:attr_name] } if model.respond_to?(:order_attrs)
87
+
88
+ names.compact.map { |name| normalize(name) }.uniq
89
+ end
90
+
91
+ # Field names of every ordering this resource advertises: the one the definition falls back to,
92
+ # the one each of its list views publishes on top of that, and the primary key.
93
+ def self.declared_sorting_fields(definition)
94
+ sortings = [definition.default_sorting]
95
+ sortings += definition.normalized_views.select { |view| view.type == :list }.map(&:default_sorting)
96
+
97
+ names = sortings.flat_map { |sorting| Habarico::OrderByHelper.field_names(sorting) }
98
+
99
+ # The primary key is advertised too, as `pk_field_name` on the resource schema: the frontend
100
+ # sorts by it whenever a list has no sorting of its own (`serializeOrderBy`). It is listed
101
+ # here rather than left to the tiebreaker, which a controller may point elsewhere.
102
+ names += Array(definition.model.try(:primary_key)).map(&:to_s)
103
+
104
+ names.uniq
105
+ end
106
+
107
+ # A `sort_by` as it arrives in `order_by`. The frontend rewrites the first `.` of the
108
+ # advertised value into `__` before sending it (`serializeOrderBy`), which is also the
109
+ # separator `order_2` splits an association off by.
110
+ def self.normalize(sort_by)
111
+ sort_by.to_s.sub('.', '__')
112
+ end
113
+
114
+ # FieldReferences declared in the resource's own list views, including the ones nested in
115
+ # boxes. A box pointing at another resource lists that resource's fields, not this one's.
116
+ def self.list_view_field_references(definition)
117
+ definition.normalized_views.select { |view| view.type == :list }.flat_map do |view|
118
+ field_references(view.boxes)
119
+ end
120
+ end
121
+ private_class_method :list_view_field_references
122
+
123
+ def self.field_references(boxes)
124
+ Array(boxes).flat_map do |item|
125
+ case item
126
+ when Habarico::Box then item.reference.present? ? [] : field_references(item.boxes)
127
+ when Habarico::FieldReference then [item]
128
+ when ::String, ::Symbol then [Habarico::FieldReference.new(name: item.to_s)]
129
+ else []
130
+ end
131
+ end
132
+ end
133
+ private_class_method :field_references
134
+
135
+ end
136
+ end
@@ -0,0 +1,46 @@
1
+ module Habarico
2
+ class ActionSchema
3
+ include ::ActiveModel::Model
4
+ include Eluvia::ActiveModel::Attributes
5
+ include Eluvia::ActiveModel::EnumAttribute
6
+ include Habarico::ExtensibleEnum
7
+
8
+ # Type of the action.
9
+ # `view` - opens a new view as redirect or dialog
10
+ # `link` - opens an external link
11
+ # `resource` - performs an action on the resource (like delete, update, etc.
12
+ string_attr :type
13
+ extensible_enum_attr :type, :ACTION_TYPE
14
+
15
+ # Flag indicating if the action uses primary key or not
16
+ boolean_attr :is_detail
17
+
18
+ # Human-readable label for the action (button)
19
+ string_attr :label
20
+
21
+ # Icon contained in the action button
22
+ string_attr :icon
23
+
24
+ # Color of the action button. Uses standard MUI color types
25
+ string_attr :color
26
+ enum_attr :color, Types::COLOR_TYPE
27
+
28
+ # Optional confirmation dialog before performing the action
29
+ has_one_attr :confirm_dialog, 'Habarico::ConfirmDialogProperties'
30
+
31
+ # Parameters specific to action type 'view'
32
+ has_one_attr :view, 'Habarico::ViewActionParameters'
33
+
34
+ # Parameters specific to action type 'resource'
35
+ has_one_attr :resource, 'Habarico::ResourceActionParameters'
36
+
37
+ # Parameters specific to action type 'link'
38
+ has_one_attr :link, 'Habarico::LinkActionParameters'
39
+
40
+ # Parameters specific to action type 'save'
41
+ has_one_attr :save, 'Habarico::SaveActionParameters'
42
+
43
+ # Parameters specific to action type 'upload-images'
44
+ has_one_attr :upload_images, 'Habarico::UploadImagesActionParameters'
45
+ end
46
+ end
@@ -0,0 +1,13 @@
1
+ module Habarico
2
+ class ConfirmDialogProperties
3
+ include ::ActiveModel::Model
4
+ include Eluvia::ActiveModel::Attributes
5
+
6
+ # Title of the confirm dialog
7
+ string_attr :title
8
+
9
+ # Message of the confirm dialog
10
+ string_attr :message
11
+
12
+ end
13
+ end