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,381 @@
1
+ module Habarico
2
+ class FieldSchemaGenerator < AbstractGenerator
3
+
4
+ def generate(model, field_name, subfields: [], force_name: nil, field_reference: nil)
5
+ # Recognise field variant (active model or active record) and validate arguments
6
+ if Habarico::ReflectionHelper.is_attributes_model_class?(model) && model.has_simple_attr?(field_name) # simple attribute on attributes model
7
+ variant = :attributes_model
8
+ attr_name = field_name
9
+ elsif Habarico::ReflectionHelper.is_attributes_model_class?(model) && model.has_complex_attr?(field_name) # complex attribute on attributes model
10
+ variant = :attributes_model
11
+ attr_name = field_name
12
+ elsif Habarico::ReflectionHelper.is_active_record_class?(model) && model.columns_hash[field_name.to_s] # standard DB column on active record
13
+ variant = :active_record
14
+ attr_name = field_name
15
+ elsif Habarico::ReflectionHelper.is_active_record_class?(model) && model.attribute_names.include?(field_name.to_s) # virtual attribute (attribute :x) - not a DB column
16
+ variant = :active_model
17
+ attr_name = field_name
18
+ elsif Habarico::ReflectionHelper.is_active_model_class?(model) && model.attribute_names.include?(field_name.to_s) # standard attribute accessor on active model
19
+ variant = :active_model
20
+ attr_name = field_name
21
+ elsif Habarico::ReflectionHelper.is_belongs_to_association?(model, field_name) # belongs to association on active record
22
+ variant = :active_record
23
+ attr_name = model.reflect_on_association(field_name.to_sym).foreign_key
24
+ elsif Habarico::ReflectionHelper.is_has_one_association?(model, field_name) # has one association on active record
25
+ variant = :attributes_model # foreign key is on the other model and must be delegated to this model via a simple attribute, so we treat it as active model attribute
26
+ attr_name = model.reflect_on_association(field_name.to_sym).foreign_key
27
+ unless Habarico::ReflectionHelper.is_attributes_model_class?(model) && model.has_simple_attr?(attr_name)
28
+ raise Eluvia::Errors::StandardError.new("Argument `model` must be an ActiveRecord::Base and Eluvia::ActiveModel::Attributes subclass and attribute `#{attr_name}` must exist on the model")
29
+ end
30
+ else
31
+ raise Eluvia::Errors::StandardError.new("Argument `model` must be an ActiveRecord::Base or Eluvia::ActiveModel::Attributes subclass and field or attribute `#{field_name}` must exist on the model")
32
+ end
33
+
34
+ # Create model
35
+ schema = Habarico::FieldSchema.new(
36
+ name: generate_name(model, field_name, attr_name, variant, force_name),
37
+ type: generate_type(model, field_name, attr_name, variant),
38
+ format: generate_format(model, field_name, attr_name, variant),
39
+ label: generate_label(model, field_name, attr_name, variant),
40
+ help_text: generate_help_text(model, field_name, attr_name, variant),
41
+ read_only: generate_read_only(model, field_name, attr_name, variant),
42
+ write_only: generate_write_only(model, field_name, attr_name, variant),
43
+ nullable: generate_nullable(model, field_name, attr_name, variant),
44
+ required: generate_required(model, field_name, attr_name, variant),
45
+ sortable: generate_sortable(model, field_name, attr_name, variant),
46
+ sort_by: generate_sort_by(model, field_name, attr_name, variant),
47
+ reference: generate_reference(model, field_name, attr_name, variant),
48
+ enum: generate_enum(model, field_name, attr_name, variant),
49
+ items: generate_items(model, field_name, attr_name, variant, subfields),
50
+ properties: generate_properties(model, field_name, attr_name, variant, subfields),
51
+ options: generate_options(model, field_name, attr_name, variant),
52
+ )
53
+
54
+ Habarico::AttributesHelper.apply_field_reference_overrides(schema, field_reference)
55
+ end
56
+
57
+ protected
58
+
59
+ # ***********************************************************************
60
+ # Schema generators
61
+ # ***********************************************************************
62
+
63
+ def enum_type_generator
64
+ @enum_type_generator ||= definition.enum_type_generator.new(definition, resource_path)
65
+ end
66
+
67
+ def field_schema_generator
68
+ @field_schema_generator ||= definition.field_schema_generator.new(definition, resource_path)
69
+ end
70
+
71
+ def fields_schema_generator
72
+ @fields_schema_generator ||= definition.fields_schema_generator.new(definition, resource_path)
73
+ end
74
+
75
+ def field_options_generator
76
+ @field_options_generator ||= definition.field_options_generator.new(definition, resource_path)
77
+ end
78
+
79
+ # ***********************************************************************
80
+ # Attribute generators
81
+ # ***********************************************************************
82
+
83
+ def generate_name(_model, field_name, _attr_name, _variant, force_name)
84
+ force_name || field_name
85
+ end
86
+
87
+ def map_active_record_type(type)
88
+ case type
89
+ when :integer then 'integer'
90
+ when :boolean then 'boolean'
91
+ when :float, :decimal then 'number'
92
+ when :datetime then 'string'
93
+ when :date then 'string'
94
+ when :text then 'string'
95
+ when :uuid then 'string'
96
+ else 'string'
97
+ end
98
+ end
99
+
100
+ def map_attributes_model_type(type)
101
+ case type
102
+ when :integer then 'integer'
103
+ when :boolean then 'boolean'
104
+ when :float then 'number'
105
+ when :datetime then 'string'
106
+ when :date then 'string'
107
+ when :string then 'string'
108
+ when :uuid then 'string'
109
+ else 'string'
110
+ end
111
+ end
112
+
113
+ def map_active_model_type(type)
114
+ case type
115
+ when 'ActiveModel::Type::Integer' then 'integer'
116
+ when 'ActiveModel::Type::String' then 'string'
117
+ else 'string'
118
+ end
119
+ end
120
+
121
+ def generate_type(model, _field_name, attr_name, variant)
122
+ # Special types and annotations
123
+ return 'object' if model.respond_to?(:has_attr?) && model.has_attr?(attr_name, type: :has_one)
124
+ return 'object' if model.respond_to?(:has_object_attr?) && model.has_object_attr?(attr_name)
125
+ return 'array' if model.respond_to?(:has_attr?) && model.has_attr?(attr_name, type: :has_many)
126
+ return 'array' if model.respond_to?(:has_array_attr?) && model.has_array_attr?(attr_name)
127
+ return 'string' if model.respond_to?(:has_enum_attr?) && model.has_enum_attr?(attr_name)
128
+
129
+ # Basic types are computed specific for active model and active record variants
130
+ if variant == :attributes_model
131
+ attr = model.find_simple_attr(attr_name)
132
+ return map_attributes_model_type(attr[:type]) if attr
133
+ end
134
+ if variant == :active_record
135
+ type = model.columns_hash[attr_name.to_s].type
136
+ return map_active_record_type(type)
137
+ end
138
+ if variant == :active_model
139
+ type = model.attribute_types[attr_name.to_s].class.to_s
140
+ return map_active_model_type(type)
141
+ end
142
+
143
+ 'string'
144
+ end
145
+
146
+ def map_active_record_format(type)
147
+ case type
148
+ when :datetime then 'date-time'
149
+ when :date then 'date'
150
+ when :time then 'time'
151
+ when :text then 'text'
152
+ when :uuid then 'uuid'
153
+ end
154
+ end
155
+
156
+ def map_attributes_model_format(type)
157
+ case type
158
+ when :datetime then 'date-time'
159
+ when :date then 'date'
160
+ when :uuid then 'uuid'
161
+ end
162
+ end
163
+
164
+ def generate_format(model, field_name, attr_name, variant)
165
+ # Special types and annotations
166
+ if model.respond_to?(:has_file_attr?) && model.has_file_attr?(attr_name)
167
+ if model.find_file_attr(attr_name).fetch(:nature) == :image
168
+ return 'base64-image'
169
+ else
170
+ return 'base64-file'
171
+ end
172
+ end
173
+ return 'range' if model.respond_to?(:has_range_attr?) && model.has_range_attr?(attr_name)
174
+ return nil if model.respond_to?(:has_array_attr?) && model.has_array_attr?(attr_name)
175
+ return nil if model.respond_to?(:has_object_attr?) && model.has_object_attr?(attr_name)
176
+ return 'relation' if Habarico::ReflectionHelper.is_has_one_reference?(model, field_name)
177
+ return 'relation' if Habarico::ReflectionHelper.is_belongs_to_association?(model, field_name)
178
+ return 'relation' if Habarico::ReflectionHelper.is_has_one_association?(model, field_name)
179
+ return 'choices' if model.respond_to?(:has_enum_attr?) && model.has_enum_attr?(attr_name)
180
+
181
+ # Basic types are computed specific for active model and active record variants
182
+ if variant == :attributes_model
183
+ attr = model.find_simple_attr(attr_name)
184
+ return map_attributes_model_format(attr[:type]) if attr
185
+ end
186
+ if variant == :active_record
187
+ type = model.columns_hash[attr_name.to_s].type
188
+ return map_active_record_format(type)
189
+ end
190
+
191
+ nil
192
+ end
193
+
194
+ def generate_label(model, field_name, _attr_name, _variant)
195
+ Habarico::AttributesHelper.generate_attr_variants(model, field_name).each do |field_name_variant|
196
+ label = I18n.t("#{model.i18n_scope}.attributes.#{model.model_name.i18n_key}.#{field_name_variant}", default: nil)
197
+ return label if label.present?
198
+ end
199
+ field_name.to_s.humanize
200
+ end
201
+
202
+ def generate_help_text(model, field_name, _attr_name, _variant)
203
+ Habarico::AttributesHelper.generate_attr_variants(model, field_name).each do |field_name_variant|
204
+ help_text = I18n.t("#{model.i18n_scope}.attributes.#{model.model_name.i18n_key}.#{field_name_variant}_help_text", default: nil)
205
+ return help_text if help_text.present?
206
+ end
207
+ nil
208
+ end
209
+
210
+ def generate_read_only(_model, _field_name, _attr_name, _variant)
211
+ # not implemented yet
212
+ end
213
+
214
+ def generate_write_only(_model, _field_name, _attr_name, _variant)
215
+ # not implemented yet
216
+ end
217
+
218
+ def generate_nullable(model, _field_name, attr_name, variant)
219
+ # For an attributes model, this is always true as there are no built-in validators
220
+ if variant == :attributes_model
221
+ return true
222
+ end
223
+
224
+ # For active record, this is derived from database column nullability
225
+ if variant == :active_record
226
+ return model.columns_hash[attr_name.to_s].null
227
+ end
228
+
229
+ # For active model, this is always true as there are no built-in validators
230
+ if variant == :active_model
231
+ return true
232
+ end
233
+
234
+ nil
235
+ end
236
+
237
+ def generate_required(model, field_name, _attr_name, variant)
238
+ # For an attributes model, this is always false as there are no built-in validators
239
+ if variant == :attributes_model
240
+ return false
241
+ end
242
+
243
+ # For active record, this is derived from presence validators
244
+ if variant == :active_record
245
+ model.validators_on(field_name).any? do |v|
246
+ v.is_a?(::ActiveModel::Validations::PresenceValidator)
247
+ end
248
+ end
249
+
250
+ # For active model, this is always false as there are no built-in validators
251
+ if variant == :active_model
252
+ return false
253
+ end
254
+
255
+ nil
256
+ end
257
+
258
+ def generate_sortable(model, field_name, attr_name, variant)
259
+ generate_sort_by(model, field_name, attr_name, variant).present?
260
+ end
261
+
262
+ def generate_sort_by(model, field_name, attr_name, _variant)
263
+ Habarico::SortingHelper.schema_sort_by(definition, model, field_name, attr_name: attr_name)
264
+ end
265
+
266
+ def generate_reference(model, field_name, _attr_name, _variant)
267
+ # Manually declared "has one" reference
268
+ if Habarico::ReflectionHelper.is_has_one_reference?(model, field_name)
269
+ reference = model.find_reference(field_name)
270
+ reference_class = reference[:class_name].constantize
271
+ if reference_class.const_defined?(:REFERENCE)
272
+ return reference_class.const_get(:REFERENCE)
273
+ end
274
+ end
275
+
276
+ # Reference derived from "belongs to" association
277
+ if Habarico::ReflectionHelper.is_belongs_to_association?(model, field_name)
278
+ reflection = model.reflect_on_association(field_name.to_sym)
279
+ if reflection
280
+ return Habarico::ResourceService.instance.find_resource_name_by_model_class(reflection.klass)
281
+ end
282
+ end
283
+
284
+ # Reference derived from "has one" association
285
+ if Habarico::ReflectionHelper.is_has_one_association?(model, field_name)
286
+ reflection = model.reflect_on_association(field_name.to_sym)
287
+ if reflection
288
+ return Habarico::ResourceService.instance.find_resource_name_by_model_class(reflection.klass)
289
+ end
290
+ end
291
+
292
+ nil
293
+ end
294
+
295
+ def generate_enum(model, _field_name, attr_name, _variant)
296
+ if model.respond_to?(:has_enum_attr?) && model.has_enum_attr?(attr_name)
297
+ return model.find_enum_attr(attr_name).keys.map do |choice|
298
+ enum_type_generator.generate(model, attr_name, choice)
299
+ end
300
+ end
301
+ nil
302
+ end
303
+
304
+ def generate_items(model, field_name, attr_name, _variant, subfields)
305
+ name = ''
306
+ label = generate_label(model, field_name, attr_name, nil)
307
+
308
+ # Manually declared "has many" reference is interpreted as array of relations, so we need to define items schema
309
+ if Habarico::ReflectionHelper.is_has_many_reference?(model, field_name)
310
+ reference = model.find_reference(field_name)
311
+ reference_class = reference[:class_name].constantize
312
+ if reference_class.const_defined?(:REFERENCE)
313
+ return Habarico::FieldSchema.new(
314
+ name: name,
315
+ label: label,
316
+ type: 'string',
317
+ format: 'relation',
318
+ reference: reference_class.const_get(:REFERENCE),
319
+ )
320
+ end
321
+ end
322
+
323
+ # "Array" attribute is interpreted as array of simple types, like string, integer, etc.
324
+ if model.respond_to?(:has_array_attr?) && model.has_array_attr?(attr_name)
325
+ array_attr = model.find_array_attr(attr_name)
326
+ return Habarico::FieldSchema.new(
327
+ name: name,
328
+ label: label,
329
+ type: map_attributes_model_type(array_attr[:type]),
330
+ format: map_attributes_model_format(array_attr[:type]),
331
+ )
332
+ end
333
+
334
+ # "Has many" attribute is interpreted as array of objects
335
+ if model.respond_to?(:has_attr?) && model.has_attr?(attr_name, type: :has_many)
336
+ array_attr = model.find_attr(attr_name, type: :has_many)
337
+ if array_attr[:class_name]
338
+ return Habarico::FieldSchema.new(
339
+ name: name,
340
+ label: label,
341
+ type: 'object',
342
+ properties: fields_schema_generator.generate(array_attr[:class_name].constantize, subfields)
343
+ )
344
+ end
345
+ end
346
+
347
+ nil
348
+ end
349
+
350
+ def generate_properties(model, _field_name, attr_name, _variant, subfields)
351
+ # "Object" attribute is just a collection of attributes existing on the same model
352
+ if model.respond_to?(:has_object_attr?) && model.has_object_attr?(attr_name)
353
+ result = []
354
+ object_attr = model.find_object_attr(attr_name)
355
+ object_attr[:fields].each do |field|
356
+ key = field.keys.first
357
+ underlying_attr_name = field.values.first
358
+ result << field_schema_generator.generate(model, underlying_attr_name, force_name: key)
359
+ end
360
+ return result
361
+ end
362
+
363
+ # "Has one" attribute is a collection of attributes existing on a different class
364
+ if model.respond_to?(:has_attr?) && model.has_attr?(attr_name, type: :has_one)
365
+ object_attr = model.find_attr(attr_name, type: :has_one)
366
+ if object_attr[:class_name] == 'Eluvia::File'
367
+ return fields_schema_generator.generate(object_attr[:class_name].constantize, %i[type url filename size content])
368
+ else
369
+ return fields_schema_generator.generate(object_attr[:class_name].constantize, subfields)
370
+ end
371
+ end
372
+
373
+ nil
374
+ end
375
+
376
+ def generate_options(model, _field_name, attr_name, _variant)
377
+ field_options_generator.generate(model, attr_name, nil)
378
+ end
379
+
380
+ end
381
+ end
@@ -0,0 +1,35 @@
1
+ module Habarico
2
+ class FieldsSchemaGenerator < AbstractGenerator
3
+
4
+ def generate(model, fields)
5
+ # Validate arguments
6
+ raise Eluvia::Errors::StandardError.new('Argument `fields` must be an Array of String, Symbol, Hash or Habarico::FieldReference instances') unless fields.is_a?(::Array) && fields.all? { |f| f.is_a?(::String) || f.is_a?(::Symbol) || f.is_a?(::Hash) || f.is_a?(Habarico::FieldReference) }
7
+
8
+ # Generate result
9
+ fields.map do |field|
10
+ field_reference = nil
11
+ if field.is_a?(::Symbol) || field.is_a?(::String)
12
+ field_name = field.to_s
13
+ subfields = []
14
+ elsif field.is_a?(Habarico::FieldReference)
15
+ field_name = field.name.to_s
16
+ subfields = []
17
+ field_reference = field # its set attributes (format, label, sortable, ...) override the model-derived ones
18
+ elsif field.is_a?(::Hash)
19
+ field_name = field.keys.first.to_s
20
+ subfields = field.values.first # We assume that this is also an array of strings / symbols / hashes, if not, the recursive call will catch it
21
+ else
22
+ next nil
23
+ end
24
+ field_schema_generator.generate(model, field_name, subfields: subfields, field_reference: field_reference)
25
+ end.compact
26
+ end
27
+
28
+ protected
29
+
30
+ def field_schema_generator
31
+ @field_schema_generator ||= definition.field_schema_generator.new(definition, resource_path)
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,201 @@
1
+ module Habarico
2
+ class FilterSchemaGenerator < AbstractGenerator
3
+
4
+ def generate(model, filter_name)
5
+ # Validate arguments
6
+ raise Eluvia::Errors::StandardError.new('Argument `model` must be an Eluvia::ActiveRecord::Filtering subclass') unless model.ancestors.include?(Eluvia::ActiveRecord::Filtering)
7
+ raise Eluvia::Errors::StandardError.new("Filter `#{filter_name}` not found on model `#{model}`") unless model.filter_params[filter_name.to_sym]
8
+ filter_param = model.filter_params[filter_name.to_sym]
9
+
10
+ # Create model
11
+ Habarico::FilterSchema.new(
12
+ name: generate_name(model, filter_name, filter_param),
13
+ type: generate_type(model, filter_name, filter_param),
14
+ format: generate_format(model, filter_name, filter_param),
15
+ label: generate_label(model, filter_name, filter_param),
16
+ help_text: generate_help_text(model, filter_name, filter_param),
17
+ reference: generate_reference(model, filter_name, filter_param),
18
+ enum: generate_enum(model, filter_name, filter_param),
19
+ items: generate_items(model, filter_name, filter_param),
20
+ properties: generate_properties(model, filter_name, filter_param),
21
+ options: generate_options(model, filter_name, filter_param)
22
+ )
23
+ end
24
+
25
+ protected
26
+
27
+ # ***********************************************************************
28
+ # Schema generators
29
+ # ***********************************************************************
30
+
31
+ def enum_type_generator
32
+ @enum_type_generator ||= definition.enum_type_generator.new(definition, resource_path)
33
+ end
34
+
35
+ def field_schema_generator
36
+ @field_schema_generator ||= definition.field_schema_generator.new(definition, resource_path)
37
+ end
38
+
39
+ def field_options_generator
40
+ @field_options_generator ||= definition.field_options_generator.new(definition, resource_path)
41
+ end
42
+
43
+ # ***********************************************************************
44
+ # Attribute generators
45
+ # ***********************************************************************
46
+
47
+ def generate_name(_model, filter_name, _filter_param)
48
+ filter_name
49
+ end
50
+
51
+ def map_type(type)
52
+ case type
53
+ when :integer then 'integer'
54
+ when :boolean then 'boolean'
55
+ when :float, :decimal then 'number'
56
+ when :datetime then 'string'
57
+ when :date then 'string'
58
+ when :text then 'string'
59
+ when :uuid then 'string'
60
+ else 'string'
61
+ end
62
+ end
63
+
64
+ def generate_type(_model, _filter_name, filter_param)
65
+ type = filter_param[:type]
66
+
67
+ # Special types and annotations
68
+ return 'object' if type == :range
69
+
70
+ # Basic types
71
+ map_type(type)
72
+ end
73
+
74
+ def map_format(type)
75
+ case type
76
+ when :integer then nil
77
+ when :boolean then nil
78
+ when :float, :decimal then nil
79
+ when :datetime then 'date-time'
80
+ when :date then 'date'
81
+ when :time then 'time'
82
+ when :text then 'text'
83
+ when :uuid then 'uuid'
84
+ end
85
+ end
86
+
87
+ def generate_format(_model, _filter_name, filter_param)
88
+ type = filter_param[:type]
89
+ attr_name = filter_param[:attr_name]
90
+ filters_model_class = filter_param[:model] # this is not the same as `_model` argument!
91
+
92
+ # Special types and annotations
93
+ return 'range' if type == :range
94
+ return 'relation' if type == :has_one
95
+ return 'relation' if filters_model_class.respond_to?(:has_attr?) && filters_model_class.has_attr?(attr_name, type: :has_one)
96
+ return 'choices' if filters_model_class.respond_to?(:has_enum_attr?) && filters_model_class.has_enum_attr?(attr_name)
97
+
98
+ # Basic types
99
+ map_format(type)
100
+ end
101
+
102
+ def generate_label(_model, _filter_name, filter_param)
103
+ return filter_param[:label] if filter_param.fetch(:label, nil).present?
104
+
105
+ attr_name = filter_param[:attr_name]
106
+ filters_model_class = filter_param[:model] # this is not the same as `_model` argument!
107
+ i18n_model_class = resolve_i18n_model_class(filters_model_class)
108
+
109
+ Habarico::AttributesHelper.generate_attr_variants(filters_model_class, attr_name).each do |attr_name_variant|
110
+ label = I18n.t("#{i18n_model_class.i18n_scope}.attributes.#{i18n_model_class.model_name.i18n_key}.#{attr_name_variant}", default: nil)
111
+ return label if label.present?
112
+ end
113
+ attr_name.to_s.humanize
114
+ end
115
+
116
+ # `filters_model_class` (`filter_param[:model]`) is usually a nested `Eluvia::ActiveRecord::FiltersModel`
117
+ # proxy class (e.g. `Employee::Filters`), not the real AR model — its own i18n_scope/model_name don't match
118
+ # where attribute translations actually live. Resolve back to the real model the same way generate_reference
119
+ # does for class names (strip the `::Filters` suffix), falling back to the proxy class itself if that fails.
120
+ def resolve_i18n_model_class(filters_model_class)
121
+ filters_model_class.name.to_s.delete_suffix('::Filters').constantize
122
+ rescue StandardError
123
+ filters_model_class
124
+ end
125
+
126
+ def generate_help_text(_model, _filter_name, _filter_param)
127
+ # not implemented yet
128
+ end
129
+
130
+ def generate_reference(_model, _filter_name, filter_param)
131
+ type = filter_param[:type]
132
+ attr_name = filter_param[:attr_name]
133
+ filters_model_class = filter_param[:model] # this is not the same as `_model` argument!
134
+
135
+ # Reference derived from filter attribute with type "has_one" on the filters model
136
+ if type == :has_one
137
+ attr = filters_model_class.find_filter_attr(attr_name)
138
+ class_name = attr.fetch(:options, nil)&.fetch(:class_name, nil)
139
+ if class_name
140
+ return Habarico::ResourceService.instance.find_resource_name_by_model_class(class_name.delete_suffix("::Filters").constantize)
141
+ end
142
+ end
143
+
144
+ # Reference derived from "has one" attribute on the filters model
145
+ if filters_model_class.respond_to?(:has_attr?) && filters_model_class.has_attr?(attr_name, type: :has_one)
146
+ attr = filters_model_class.find_attr(attr_name, type: :has_one)
147
+ class_name = attr.fetch(:class_name, nil)
148
+ if class_name
149
+ return Habarico::ResourceService.instance.find_resource_name_by_model_class(class_name.delete_suffix("::Filters").constantize)
150
+ end
151
+ end
152
+
153
+ nil
154
+ end
155
+
156
+ def generate_enum(_model, _filter_name, filter_param)
157
+ attr_name = filter_param[:attr_name]
158
+ filters_model_class = filter_param[:model] # this is not the same as `_model` argument!
159
+
160
+ # Generate enum item schema for each choice in enum attribute
161
+ if filters_model_class.respond_to?(:has_enum_attr?) && filters_model_class.has_enum_attr?(attr_name)
162
+ return filters_model_class.find_enum_attr(attr_name).keys.map do |choice|
163
+ enum_type_generator.generate(filters_model_class, attr_name, choice)
164
+ end
165
+ end
166
+
167
+ nil
168
+ end
169
+
170
+ def generate_items(_model, _filter_name, _filter_param)
171
+ # not implemented yet
172
+ end
173
+
174
+ def generate_properties(_model, _filter_name, filter_param)
175
+ attr_name = filter_param[:attr_name]
176
+ filters_model_class = filter_param[:model] # this is not the same as `_model` argument!
177
+
178
+ # Object attribute is just a collection of attributes existing on the same model. In this case it's
179
+ # usually a range attribute which is defined as a special case of object.
180
+ if filters_model_class.respond_to?(:has_object_attr?) && filters_model_class.has_object_attr?(attr_name)
181
+ result = []
182
+ object_attr = filters_model_class.find_object_attr(attr_name)
183
+ object_attr[:fields].each do |field|
184
+ key = field.keys.first
185
+ underlying_attr_name = field.values.first
186
+ result << field_schema_generator.generate(filters_model_class, underlying_attr_name, force_name: key)
187
+ end
188
+ return result
189
+ end
190
+
191
+ nil
192
+ end
193
+
194
+ def generate_options(_model, _filter_name, filter_param)
195
+ attr_name = filter_param[:attr_name]
196
+ filters_model_class = filter_param[:model] # this is not the same as `_model` argument!
197
+ field_options_generator.generate(filters_model_class, attr_name, nil)
198
+ end
199
+
200
+ end
201
+ end
@@ -0,0 +1,25 @@
1
+ module Habarico
2
+ class FiltersSchemaGenerator < AbstractGenerator
3
+
4
+ def generate(model, filters)
5
+ # Validate arguments
6
+ raise Eluvia::Errors::StandardError.new('Argument `filters` must be an Array of String or Symbol instances') unless filters.is_a?(::Array) && filters.all? { |f| f.is_a?(::String) || f.is_a?(::Symbol) }
7
+
8
+ # Generate result
9
+ result = filters.map { |filter_name| filter_schema_generator.generate(model, filter_name) }.compact
10
+ result << search_filter_schema_generator.generate(model)
11
+ result
12
+ end
13
+
14
+ protected
15
+
16
+ def filter_schema_generator
17
+ @filter_schema_generator ||= definition.filter_schema_generator.new(definition, resource_path)
18
+ end
19
+
20
+ def search_filter_schema_generator
21
+ @search_filter_schema_generator ||= definition.search_filter_schema_generator.new(definition, resource_path)
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,34 @@
1
+ module Habarico
2
+ class MethodSchemaGenerator < AbstractGenerator
3
+
4
+ def generate(model, fields, filters)
5
+ Habarico::MethodSchema.new(
6
+ fields: generate_fields(model, fields, filters),
7
+ filters: generate_filters(model, fields, filters)
8
+ )
9
+ end
10
+
11
+ protected
12
+
13
+ def fields_schema_generator
14
+ @fields_schema_generator ||= definition.fields_schema_generator.new(definition, resource_path)
15
+ end
16
+
17
+ def filters_schema_generator
18
+ @filters_schema_generator ||= definition.filters_schema_generator.new(definition, resource_path)
19
+ end
20
+
21
+ # ***********************************************************************
22
+ # Attribute generators
23
+ # ***********************************************************************
24
+
25
+ def generate_fields(model, fields, _filters)
26
+ fields_schema_generator.generate(model, fields)
27
+ end
28
+
29
+ def generate_filters(model, _fields, filters)
30
+ filters_schema_generator.generate(model, filters)
31
+ end
32
+
33
+ end
34
+ end