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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +82 -0
- data/Rakefile +15 -0
- data/VERSION +1 -0
- data/app/controllers/habarico/action_schema_controller.rb +41 -0
- data/app/controllers/habarico/application_controller.rb +15 -0
- data/app/controllers/habarico/contrib/preferences/user_preferences_controller.rb +105 -0
- data/app/controllers/habarico/global_schema_controller.rb +11 -0
- data/app/controllers/habarico/local_schema_controller.rb +35 -0
- data/app/helpers/habarico/application_helper.rb +4 -0
- data/app/models/habarico/application_record.rb +5 -0
- data/app/views/layouts/habarico/application.html.erb +17 -0
- data/config/locales/cs.yml +39 -0
- data/config/locales/de.yml +39 -0
- data/config/locales/en.yml +39 -0
- data/config/locales/es.yml +39 -0
- data/config/locales/fr.yml +39 -0
- data/config/locales/it.yml +39 -0
- data/config/locales/ja.yml +39 -0
- data/config/locales/nl.yml +39 -0
- data/config/locales/pl.yml +39 -0
- data/config/locales/pt.yml +39 -0
- data/config/locales/ro.yml +39 -0
- data/config/locales/sk.yml +39 -0
- data/config/locales/uk.yml +39 -0
- data/config/routes.rb +1 -0
- data/lib/generators/habarico/preferences/install_generator.rb +27 -0
- data/lib/generators/habarico/preferences/templates/create_habarico_user_preferences.rb.tt +15 -0
- data/lib/habarico/active_model/references.rb +68 -0
- data/lib/habarico/authenticatable.rb +31 -0
- data/lib/habarico/config.rb +71 -0
- data/lib/habarico/context.rb +126 -0
- data/lib/habarico/contrib/preferences/base_preferences.rb +40 -0
- data/lib/habarico/contrib/preferences/config.rb +31 -0
- data/lib/habarico/contrib/preferences/errors.rb +79 -0
- data/lib/habarico/contrib/preferences/locales/cs.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/de.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/en.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/es.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/fr.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/it.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/ja.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/nl.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/pl.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/pt.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/ro.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/sk.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/uk.yml +8 -0
- data/lib/habarico/contrib/preferences/registry.rb +293 -0
- data/lib/habarico/contrib/preferences/routes.rb +17 -0
- data/lib/habarico/contrib/preferences/schemas.rb +129 -0
- data/lib/habarico/contrib/preferences/serializer.rb +19 -0
- data/lib/habarico/contrib/preferences/user_preference.rb +16 -0
- data/lib/habarico/contrib/preferences.rb +32 -0
- data/lib/habarico/controller.rb +584 -0
- data/lib/habarico/definition/action_reference.rb +18 -0
- data/lib/habarico/definition/actions/action.rb +14 -0
- data/lib/habarico/definition/actions/create_action.rb +18 -0
- data/lib/habarico/definition/actions/delete_action.rb +20 -0
- data/lib/habarico/definition/actions/edit_action.rb +18 -0
- data/lib/habarico/definition/actions/link_action.rb +11 -0
- data/lib/habarico/definition/actions/resource_action.rb +12 -0
- data/lib/habarico/definition/actions/save_action.rb +14 -0
- data/lib/habarico/definition/actions/show_action.rb +18 -0
- data/lib/habarico/definition/actions/upload_images_action.rb +11 -0
- data/lib/habarico/definition/actions/view_action.rb +11 -0
- data/lib/habarico/definition/boxes/array_box.rb +10 -0
- data/lib/habarico/definition/boxes/box.rb +32 -0
- data/lib/habarico/definition/boxes/gallery_box.rb +10 -0
- data/lib/habarico/definition/boxes/grid_box.rb +10 -0
- data/lib/habarico/definition/boxes/grid_item_box.rb +10 -0
- data/lib/habarico/definition/boxes/list_box.rb +10 -0
- data/lib/habarico/definition/boxes/object_box.rb +10 -0
- data/lib/habarico/definition/boxes/tab_box.rb +10 -0
- data/lib/habarico/definition/boxes/tab_item_box.rb +10 -0
- data/lib/habarico/definition/boxes/wizard_box.rb +10 -0
- data/lib/habarico/definition/concerns/definition_model.rb +38 -0
- data/lib/habarico/definition/definition.rb +206 -0
- data/lib/habarico/definition/field_reference.rb +9 -0
- data/lib/habarico/definition/filter_reference.rb +7 -0
- data/lib/habarico/definition/menu/link_menu_item.rb +12 -0
- data/lib/habarico/definition/menu/menu_item.rb +9 -0
- data/lib/habarico/definition/menu/text_menu_item.rb +12 -0
- data/lib/habarico/definition/menu/view_menu_item.rb +13 -0
- data/lib/habarico/definition/response.rb +13 -0
- data/lib/habarico/definition/views/action_view.rb +11 -0
- data/lib/habarico/definition/views/create_view.rb +15 -0
- data/lib/habarico/definition/views/edit_view.rb +15 -0
- data/lib/habarico/definition/views/list_view.rb +17 -0
- data/lib/habarico/definition/views/show_view.rb +15 -0
- data/lib/habarico/definition/views/view.rb +10 -0
- data/lib/habarico/engine.rb +6 -0
- data/lib/habarico/file_validations.rb +119 -0
- data/lib/habarico/generators/abstract_generator.rb +21 -0
- data/lib/habarico/generators/action/action_schema_generator.rb +111 -0
- data/lib/habarico/generators/action/confirm_dialog_properties_generator.rb +30 -0
- data/lib/habarico/generators/action/global_resource_action_schema_generator.rb +46 -0
- data/lib/habarico/generators/action/link_action_parameters_generator.rb +31 -0
- data/lib/habarico/generators/action/resource_action_endpoint_parameters_generator.rb +70 -0
- data/lib/habarico/generators/action/resource_action_parameters_generator.rb +40 -0
- data/lib/habarico/generators/action/resource_action_schema_generator.rb +51 -0
- data/lib/habarico/generators/action/save_action_parameters_generator.rb +26 -0
- data/lib/habarico/generators/action/upload_images_action_parameters_generator.rb +41 -0
- data/lib/habarico/generators/action/view_action_endpoint_parameters_generator.rb +37 -0
- data/lib/habarico/generators/action/view_action_parameters_generator.rb +56 -0
- data/lib/habarico/generators/endpoint/dynamic_render_options_generator.rb +26 -0
- data/lib/habarico/generators/endpoint/endpoints_schema_generator.rb +61 -0
- data/lib/habarico/generators/endpoint/enum_type_generator.rb +45 -0
- data/lib/habarico/generators/endpoint/field_options_generator.rb +179 -0
- data/lib/habarico/generators/endpoint/field_schema_generator.rb +381 -0
- data/lib/habarico/generators/endpoint/fields_schema_generator.rb +35 -0
- data/lib/habarico/generators/endpoint/filter_schema_generator.rb +201 -0
- data/lib/habarico/generators/endpoint/filters_schema_generator.rb +25 -0
- data/lib/habarico/generators/endpoint/method_schema_generator.rb +34 -0
- data/lib/habarico/generators/endpoint/number_field_options_generator.rb +45 -0
- data/lib/habarico/generators/endpoint/phone_field_options_generator.rb +29 -0
- data/lib/habarico/generators/endpoint/relation_field_options_generator.rb +90 -0
- data/lib/habarico/generators/endpoint/search_filter_schema_generator.rb +44 -0
- data/lib/habarico/generators/menu/menu_group_schema_generator.rb +63 -0
- data/lib/habarico/generators/menu/menu_link_parameters_generator.rb +31 -0
- data/lib/habarico/generators/menu/menu_schema_generator.rb +105 -0
- data/lib/habarico/generators/menu/menu_view_endpoint_parameters_generator.rb +44 -0
- data/lib/habarico/generators/menu/menu_view_parameters_generator.rb +50 -0
- data/lib/habarico/generators/resource/action_resource_schema_generator.rb +84 -0
- data/lib/habarico/generators/resource/global_resource_schema_generator.rb +70 -0
- data/lib/habarico/generators/resource/local_resource_schema_generator.rb +111 -0
- data/lib/habarico/generators/resource/object_resource_schema_generator.rb +49 -0
- data/lib/habarico/generators/resource/resource_schema_generator.rb +31 -0
- data/lib/habarico/generators/view/action_descriptor_generator.rb +88 -0
- data/lib/habarico/generators/view/array_box_parameters_generator.rb +34 -0
- data/lib/habarico/generators/view/box_options_generator.rb +23 -0
- data/lib/habarico/generators/view/box_schema_generator.rb +194 -0
- data/lib/habarico/generators/view/detail_view_parameters_generator.rb +57 -0
- data/lib/habarico/generators/view/field_descriptor_generator.rb +107 -0
- data/lib/habarico/generators/view/filter_descriptor_generator.rb +75 -0
- data/lib/habarico/generators/view/gallery_box_parameters_generator.rb +71 -0
- data/lib/habarico/generators/view/global_view_schema_generator.rb +59 -0
- data/lib/habarico/generators/view/grid_item_box_parameters_generator.rb +44 -0
- data/lib/habarico/generators/view/list_box_parameters_generator.rb +124 -0
- data/lib/habarico/generators/view/list_view_parameters_generator.rb +120 -0
- data/lib/habarico/generators/view/object_box_parameters_generator.rb +26 -0
- data/lib/habarico/generators/view/sort_descriptor_generator.rb +30 -0
- data/lib/habarico/generators/view/tab_box_parameters_generator.rb +26 -0
- data/lib/habarico/generators/view/view_endpoint_parameters_generator.rb +71 -0
- data/lib/habarico/generators/view/view_schema_generator.rb +87 -0
- data/lib/habarico/generators/view/wizard_box_parameters_generator.rb +32 -0
- data/lib/habarico/helpers/action_helper.rb +9 -0
- data/lib/habarico/helpers/attributes_helper.rb +67 -0
- data/lib/habarico/helpers/box_helper.rb +19 -0
- data/lib/habarico/helpers/callable_helper.rb +15 -0
- data/lib/habarico/helpers/filter_params_helper.rb +49 -0
- data/lib/habarico/helpers/order_by_helper.rb +56 -0
- data/lib/habarico/helpers/reflection_helper.rb +61 -0
- data/lib/habarico/helpers/sorting_helper.rb +136 -0
- data/lib/habarico/schema/action/action_schema.rb +46 -0
- data/lib/habarico/schema/action/confirm_dialog_properties.rb +13 -0
- data/lib/habarico/schema/action/global_resource_action_schema.rb +29 -0
- data/lib/habarico/schema/action/link_action_parameters.rb +15 -0
- data/lib/habarico/schema/action/resource_action_endpoint_parameters.rb +24 -0
- data/lib/habarico/schema/action/resource_action_parameters.rb +17 -0
- data/lib/habarico/schema/action/resource_action_schema.rb +14 -0
- data/lib/habarico/schema/action/save_action_parameters.rb +10 -0
- data/lib/habarico/schema/action/upload_images_action_parameters.rb +23 -0
- data/lib/habarico/schema/action/view_action_endpoint_parameters.rb +16 -0
- data/lib/habarico/schema/action/view_action_parameters.rb +27 -0
- data/lib/habarico/schema/endpoint/ai_field_options.rb +10 -0
- data/lib/habarico/schema/endpoint/box_options.rb +10 -0
- data/lib/habarico/schema/endpoint/choices_field_options.rb +10 -0
- data/lib/habarico/schema/endpoint/dynamic_render_options.rb +14 -0
- data/lib/habarico/schema/endpoint/endpoints_schema.rb +22 -0
- data/lib/habarico/schema/endpoint/enum_type.rb +21 -0
- data/lib/habarico/schema/endpoint/field_options.rb +56 -0
- data/lib/habarico/schema/endpoint/field_schema.rb +65 -0
- data/lib/habarico/schema/endpoint/filter_schema.rb +44 -0
- data/lib/habarico/schema/endpoint/image_field_options.rb +13 -0
- data/lib/habarico/schema/endpoint/key_value.rb +13 -0
- data/lib/habarico/schema/endpoint/method_schema.rb +13 -0
- data/lib/habarico/schema/endpoint/number_field_options.rb +24 -0
- data/lib/habarico/schema/endpoint/phone_field_options.rb +13 -0
- data/lib/habarico/schema/endpoint/relation_field_options.rb +39 -0
- data/lib/habarico/schema/extensible_enum.rb +20 -0
- data/lib/habarico/schema/menu/action_view_choice.rb +13 -0
- data/lib/habarico/schema/menu/menu_group_schema.rb +26 -0
- data/lib/habarico/schema/menu/menu_schema.rb +36 -0
- data/lib/habarico/schema/resource/global_resource_schema.rb +14 -0
- data/lib/habarico/schema/resource/local_resource_schema.rb +14 -0
- data/lib/habarico/schema/resource/object_resource_schema.rb +8 -0
- data/lib/habarico/schema/resource/resource_schema.rb +16 -0
- data/lib/habarico/schema/types.rb +102 -0
- data/lib/habarico/schema/view/action_descriptor.rb +30 -0
- data/lib/habarico/schema/view/array_box_parameters.rb +13 -0
- data/lib/habarico/schema/view/box_schema.rb +67 -0
- data/lib/habarico/schema/view/detail_view_parameters.rb +16 -0
- data/lib/habarico/schema/view/field_descriptor.rb +50 -0
- data/lib/habarico/schema/view/field_wrapper_properties.rb +13 -0
- data/lib/habarico/schema/view/filter_descriptor.rb +24 -0
- data/lib/habarico/schema/view/gallery_box_parameters.rb +31 -0
- data/lib/habarico/schema/view/global_view_schema.rb +25 -0
- data/lib/habarico/schema/view/grid_item_box_parameters.rb +13 -0
- data/lib/habarico/schema/view/list_box_parameters.rb +44 -0
- data/lib/habarico/schema/view/list_view_parameters.rb +31 -0
- data/lib/habarico/schema/view/object_box_parameters.rb +10 -0
- data/lib/habarico/schema/view/sort_descriptor.rb +15 -0
- data/lib/habarico/schema/view/sortable_parameters.rb +13 -0
- data/lib/habarico/schema/view/tab_box_parameters.rb +10 -0
- data/lib/habarico/schema/view/view_action_descriptor_parameters.rb +21 -0
- data/lib/habarico/schema/view/view_endpoint_parameters.rb +22 -0
- data/lib/habarico/schema/view/view_schema.rb +33 -0
- data/lib/habarico/schema/view/wizard_box_parameters.rb +13 -0
- data/lib/habarico/search/field_resolver.rb +138 -0
- data/lib/habarico/search/search_matcher.rb +152 -0
- data/lib/habarico/serializers/meta_serializer.rb +42 -0
- data/lib/habarico/serializers/record_serializer.rb +33 -0
- data/lib/habarico/serializers/serializer.rb +61 -0
- data/lib/habarico/services/resource_service.rb +69 -0
- data/lib/habarico/services/routing_service.rb +160 -0
- data/lib/habarico/services/schema_service.rb +72 -0
- data/lib/habarico/version.rb +5 -0
- data/lib/habarico.rb +262 -0
- metadata +324 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Habarico
|
|
2
|
+
class WizardBoxParameters
|
|
3
|
+
include ::ActiveModel::Model
|
|
4
|
+
include Eluvia::ActiveModel::Attributes
|
|
5
|
+
|
|
6
|
+
# Show previous/next navigation buttons in the box toolbar
|
|
7
|
+
boolean_attr :show_toolbar_navigation
|
|
8
|
+
|
|
9
|
+
# Validate the current step before moving to the next one
|
|
10
|
+
boolean_attr :validate_steps
|
|
11
|
+
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
module Habarico
|
|
2
|
+
module Search
|
|
3
|
+
|
|
4
|
+
# A resolved leaf field of a `search_fields` path.
|
|
5
|
+
#
|
|
6
|
+
# `model` is the class owning the leaf, `name` the leaf attribute name, `path` the original
|
|
7
|
+
# (possibly relation-traversing) path, `type` the search classification and `column` the
|
|
8
|
+
# ActiveRecord column backing the leaf (nil for pure attributes-model attributes).
|
|
9
|
+
LeafField = Struct.new(:model, :name, :path, :type, :column, keyword_init: true) do
|
|
10
|
+
|
|
11
|
+
def label
|
|
12
|
+
return name.to_s unless model.respond_to?(:human_attribute_name)
|
|
13
|
+
|
|
14
|
+
model.human_attribute_name(name)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def choices?
|
|
18
|
+
type == :choices
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def boolean?
|
|
22
|
+
type == :boolean
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def numeric?
|
|
26
|
+
type == :numeric
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def text?
|
|
30
|
+
type == :text
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Resolves a `search_fields` path (e.g. `author__name`) against a model and classifies the
|
|
36
|
+
# leaf field's type. Ruby port of the Python `walk_to_leaf_field` helper, extended with the
|
|
37
|
+
# type detection used by the search matcher and highlighter - it is the single source of truth
|
|
38
|
+
# both build on.
|
|
39
|
+
#
|
|
40
|
+
# Intermediate hops must be relations (ActiveRecord associations or Habarico references); the
|
|
41
|
+
# leaf must be an ActiveRecord column or an enum attribute. Unresolvable paths yield `nil`
|
|
42
|
+
# (mirrors the Python behaviour, so a stale entry never crashes the request); use `resolve!`
|
|
43
|
+
# to raise instead - handy for catching typos in tests.
|
|
44
|
+
module FieldResolver
|
|
45
|
+
|
|
46
|
+
RELATION_SEPARATOR = '__'.freeze
|
|
47
|
+
NUMERIC_COLUMN_TYPES = %i[integer float decimal].freeze
|
|
48
|
+
|
|
49
|
+
# Returns a LeafField for `path` on `model`, or nil when it cannot be resolved.
|
|
50
|
+
def self.resolve(model, path)
|
|
51
|
+
return nil if model.nil? || path.nil?
|
|
52
|
+
|
|
53
|
+
# Negative limit keeps trailing empty segments so `title__` is rejected too.
|
|
54
|
+
parts = path.to_s.split(RELATION_SEPARATOR, -1)
|
|
55
|
+
return nil if parts.empty? || parts.any?(&:empty?)
|
|
56
|
+
|
|
57
|
+
current = model
|
|
58
|
+
parts[0..-2].each do |part|
|
|
59
|
+
current = step_into_relation(current, part)
|
|
60
|
+
return nil if current.nil?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
build_leaf(current, parts.last, path.to_s)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Like `resolve`, but raises ArgumentError when the path cannot be resolved.
|
|
67
|
+
def self.resolve!(model, path)
|
|
68
|
+
resolve(model, path) || raise(ArgumentError, "Cannot resolve search field path #{path.inspect} on #{model.inspect}")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Follows one relation hop, returning the associated model class or nil when `part` is not a
|
|
72
|
+
# traversable relation. Prefers ActiveRecord associations, falls back to Habarico references.
|
|
73
|
+
def self.step_into_relation(model, part)
|
|
74
|
+
if model.respond_to?(:reflect_on_association)
|
|
75
|
+
reflection = model.reflect_on_association(part.to_sym)
|
|
76
|
+
return reflection_klass(reflection) if reflection
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
if model.respond_to?(:has_reference?) && model.has_reference?(part) && model.respond_to?(:find_reference)
|
|
80
|
+
return reference_klass(model.find_reference(part))
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
nil
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# The target class of an ActiveRecord reflection, or nil for polymorphic associations that
|
|
87
|
+
# have no single class.
|
|
88
|
+
def self.reflection_klass(reflection)
|
|
89
|
+
reflection.klass
|
|
90
|
+
rescue StandardError
|
|
91
|
+
nil
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# The target class of a Habarico reference descriptor (`class_name` may be a Class or String).
|
|
95
|
+
def self.reference_klass(reference)
|
|
96
|
+
return nil if reference.nil?
|
|
97
|
+
|
|
98
|
+
class_name = reference[:class_name]
|
|
99
|
+
class_name.is_a?(::String) ? class_name.constantize : class_name
|
|
100
|
+
rescue NameError
|
|
101
|
+
nil
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def self.build_leaf(model, name, path)
|
|
105
|
+
type = detect_type(model, name)
|
|
106
|
+
return nil if type.nil?
|
|
107
|
+
|
|
108
|
+
LeafField.new(model: model, name: name, path: path, type: type, column: column_for(model, name))
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Classifies the leaf field. Enum attributes are choices (regardless of their underlying
|
|
112
|
+
# column type); otherwise the ActiveRecord column type decides. An unknown leaf yields nil.
|
|
113
|
+
def self.detect_type(model, name)
|
|
114
|
+
return :choices if enum?(model, name)
|
|
115
|
+
|
|
116
|
+
column = column_for(model, name)
|
|
117
|
+
return nil if column.nil?
|
|
118
|
+
|
|
119
|
+
case column.type
|
|
120
|
+
when :boolean then :boolean
|
|
121
|
+
when *NUMERIC_COLUMN_TYPES then :numeric
|
|
122
|
+
else :text
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def self.enum?(model, name)
|
|
127
|
+
model.respond_to?(:has_enum_attr?) && model.has_enum_attr?(name)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def self.column_for(model, name)
|
|
131
|
+
return nil unless model.respond_to?(:columns_hash)
|
|
132
|
+
|
|
133
|
+
model.columns_hash[name.to_s]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
module Habarico
|
|
2
|
+
module Search
|
|
3
|
+
|
|
4
|
+
# Applies a typed fulltext search over a resource's `search_fields`. Ruby port of the Python
|
|
5
|
+
# `SearchFilter` (MR !198): Habarico owns the whole search query rather than delegating to
|
|
6
|
+
# the base library's `filter_2!({search:})`, whose `apply_search` matches a separate FiltersModel field
|
|
7
|
+
# list, cannot traverse relations and always unaccents. Owning it here makes
|
|
8
|
+
# `Definition#search_fields` the single source of truth shared with SearchHighlighter and lets
|
|
9
|
+
# `Config.search_unaccent_enabled` govern matching and highlighting consistently.
|
|
10
|
+
#
|
|
11
|
+
# Semantics mirror the Python matcher: AND across query words, OR across fields. Per field the
|
|
12
|
+
# leaf type (from FieldResolver) decides the match:
|
|
13
|
+
#
|
|
14
|
+
# - text -> `lower([unaccent](col)) LIKE %word%`
|
|
15
|
+
# - choices -> the word is matched against the human-readable enum labels; the resulting raw
|
|
16
|
+
# values feed an `IN (...)` lookup (no match -> the field contributes nothing)
|
|
17
|
+
# - boolean -> matched against the translated Yes/No labels -> `IN (true/false)`
|
|
18
|
+
# - numeric -> `lower(CAST(col AS text)) LIKE %word%`
|
|
19
|
+
#
|
|
20
|
+
# Relations (`author__name`) are reached with `left_outer_joins`; matches are collected as
|
|
21
|
+
# primary keys and folded back with `where(pk IN ...)` so joins never duplicate rows or skew
|
|
22
|
+
# kaminari's `total_count`.
|
|
23
|
+
class SearchMatcher
|
|
24
|
+
|
|
25
|
+
# Unicode combining marks, dropped when matching diacritics-insensitively. Lives here, in the
|
|
26
|
+
# free core, because the free matcher needs it; a pro/enterprise highlighter reuses it from
|
|
27
|
+
# here rather than the other way round (HAB-812).
|
|
28
|
+
COMBINING_MARK = /\p{Mn}/
|
|
29
|
+
|
|
30
|
+
def initialize(model:, search_fields:, unaccent: nil)
|
|
31
|
+
@model = model
|
|
32
|
+
@search_fields = search_fields.to_a
|
|
33
|
+
@unaccent = unaccent.nil? ? Habarico::Config.search_unaccent_enabled : unaccent
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Narrows `scope` to records matching every query word in at least one `search_fields` path.
|
|
37
|
+
# Returns `scope` unchanged when the query is blank or no search fields are declared.
|
|
38
|
+
def apply(scope, query)
|
|
39
|
+
return scope if query.nil?
|
|
40
|
+
|
|
41
|
+
words = query.to_s.split(/\s+/).reject(&:empty?)
|
|
42
|
+
return scope if words.empty? || @search_fields.empty?
|
|
43
|
+
|
|
44
|
+
matched = nil
|
|
45
|
+
words.each do |word|
|
|
46
|
+
word_ids = ids_for_word(scope, word)
|
|
47
|
+
matched = matched.nil? ? word_ids : (matched & word_ids)
|
|
48
|
+
break if matched.empty?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
scope.where(primary_key => matched || [])
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def primary_key
|
|
57
|
+
@model.primary_key
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Primary keys of records where any field matches `word` (OR across fields).
|
|
61
|
+
def ids_for_word(scope, word)
|
|
62
|
+
@search_fields.each_with_object([]) do |path, acc|
|
|
63
|
+
leaf = FieldResolver.resolve(@model, path)
|
|
64
|
+
next if leaf.nil?
|
|
65
|
+
|
|
66
|
+
relation = field_relation(scope, path, leaf, word)
|
|
67
|
+
acc.concat(relation.pluck(primary_key)) unless relation.nil?
|
|
68
|
+
end.uniq
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# A relation selecting records where `leaf` matches `word`, or nil when the field cannot
|
|
72
|
+
# match this word at all (e.g. a choices/boolean word matching no label).
|
|
73
|
+
def field_relation(scope, path, leaf, word)
|
|
74
|
+
joined = join_for(scope, path)
|
|
75
|
+
column = qualified_column(leaf)
|
|
76
|
+
|
|
77
|
+
if leaf.choices?
|
|
78
|
+
values = matching_choice_values(leaf, word)
|
|
79
|
+
values.empty? ? nil : joined.where("#{column} IN (?)", values)
|
|
80
|
+
elsif leaf.boolean?
|
|
81
|
+
values = matching_boolean_values(word)
|
|
82
|
+
values.empty? ? nil : joined.where("#{column} IN (?)", values)
|
|
83
|
+
elsif leaf.numeric?
|
|
84
|
+
joined.where("lower(CAST(#{column} AS text)) LIKE ?", like_pattern(word, unaccent: false))
|
|
85
|
+
else
|
|
86
|
+
joined.where("lower(#{text_expr(column)}) LIKE ?", like_pattern(word, unaccent: @unaccent))
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Left-joins the relation hops of `path` so a leaf on a related table can be referenced.
|
|
91
|
+
def join_for(scope, path)
|
|
92
|
+
hops = path.to_s.split(FieldResolver::RELATION_SEPARATOR)[0..-2].map(&:to_sym)
|
|
93
|
+
return scope if hops.empty?
|
|
94
|
+
|
|
95
|
+
association = hops.reverse.reduce { |nested, hop| { hop => nested } }
|
|
96
|
+
scope.left_outer_joins(association)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# `table.column` for the leaf, using the leaf's owning table (the joined table for relations).
|
|
100
|
+
def qualified_column(leaf)
|
|
101
|
+
"#{leaf.model.table_name}.#{leaf.name}"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Wraps the column in `unaccent(...)` when diacritics-insensitive matching is on.
|
|
105
|
+
def text_expr(column)
|
|
106
|
+
@unaccent ? "unaccent(#{column})" : column
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# A `%word%` LIKE pattern, lowercased and (optionally) accent-stripped to match `text_expr`.
|
|
110
|
+
def like_pattern(word, unaccent:)
|
|
111
|
+
normalized = normalize(word, unaccent: unaccent)
|
|
112
|
+
"%#{normalized}%"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Raw enum values whose human-readable label contains `word`.
|
|
116
|
+
def matching_choice_values(leaf, word)
|
|
117
|
+
choices = leaf.model.find_enum_attr(leaf.name)
|
|
118
|
+
return [] if choices.nil?
|
|
119
|
+
|
|
120
|
+
needle = normalize(word, unaccent: @unaccent)
|
|
121
|
+
choices.values
|
|
122
|
+
.select { |choice| normalize(choice[:label], unaccent: @unaccent).include?(needle) }
|
|
123
|
+
.map { |choice| choice[:value] }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Boolean raw values whose translated Yes/No label contains `word`.
|
|
127
|
+
def matching_boolean_values(word)
|
|
128
|
+
needle = normalize(word, unaccent: @unaccent)
|
|
129
|
+
values = []
|
|
130
|
+
values << true if normalize(yes_label, unaccent: @unaccent).include?(needle)
|
|
131
|
+
values << false if normalize(no_label, unaccent: @unaccent).include?(needle)
|
|
132
|
+
values
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def normalize(value, unaccent:)
|
|
136
|
+
value = value.to_s.downcase
|
|
137
|
+
return value unless unaccent
|
|
138
|
+
|
|
139
|
+
value.unicode_normalize(:nfkd).each_char.grep_v(COMBINING_MARK).join
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def yes_label
|
|
143
|
+
defined?(I18n) ? I18n.t('search.yes', default: 'Yes') : 'Yes'
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def no_label
|
|
147
|
+
defined?(I18n) ? I18n.t('search.no', default: 'No') : 'No'
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Habarico
|
|
2
|
+
module Serializers
|
|
3
|
+
class MetaSerializer
|
|
4
|
+
include Eluvia::Serializers::FieldsetSerializer
|
|
5
|
+
|
|
6
|
+
class SchemaSerializer
|
|
7
|
+
include Eluvia::Serializers::FieldsetSerializer
|
|
8
|
+
|
|
9
|
+
READABLE_FIELDS = %w[name label pk_field_name actions].freeze
|
|
10
|
+
|
|
11
|
+
def readable_fields
|
|
12
|
+
READABLE_FIELDS
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def read_attribute(record, field_name)
|
|
16
|
+
record[field_name]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
READABLE_FIELDS = %w[repr schema tags trigger_action].freeze
|
|
21
|
+
|
|
22
|
+
def initialize(schema_serializer: SchemaSerializer.new)
|
|
23
|
+
@schema_serializer = schema_serializer
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def readable_fields
|
|
27
|
+
READABLE_FIELDS
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def serialize(data, fields: nil)
|
|
31
|
+
read_fieldset(fields).to_h { |rest_field| [rest_field.name, read_attribute(data, rest_field.name, rest_field.fieldset)] }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def read_attribute(data, field_name, sub_fieldset = nil)
|
|
35
|
+
return data[field_name] unless field_name == 'schema'
|
|
36
|
+
|
|
37
|
+
@schema_serializer.serialize(data['schema'], fields: sub_fieldset&.to_s)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Habarico
|
|
2
|
+
module Serializers
|
|
3
|
+
class RecordSerializer
|
|
4
|
+
include Eluvia::Serializers::FieldsetSerializer
|
|
5
|
+
|
|
6
|
+
META_FIELD = 'meta'.freeze
|
|
7
|
+
|
|
8
|
+
def initialize(serializer:, meta_serializer:)
|
|
9
|
+
@serializer = serializer
|
|
10
|
+
@meta_serializer = meta_serializer
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def readable_fields
|
|
14
|
+
@serializer.readable_fields + [META_FIELD]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def serialize(record, meta, fields: nil)
|
|
18
|
+
read_fieldset(fields).each_with_object({}) do |rest_field, result|
|
|
19
|
+
next if rest_field.name != META_FIELD && !record.respond_to?(rest_field.name)
|
|
20
|
+
|
|
21
|
+
result[rest_field.name] = read_attribute(record, rest_field.name, rest_field.fieldset, meta)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def read_attribute(record, field_name, sub_fieldset, meta)
|
|
26
|
+
return @meta_serializer.serialize(meta, fields: sub_fieldset&.to_s) if field_name == META_FIELD
|
|
27
|
+
|
|
28
|
+
@serializer.read_attribute(record, field_name)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Habarico
|
|
2
|
+
module Serializers
|
|
3
|
+
class Serializer
|
|
4
|
+
include Eluvia::Serializers::FieldsetSerializer
|
|
5
|
+
|
|
6
|
+
def initialize(definition)
|
|
7
|
+
@definition = definition
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def serialize(record, fields: nil)
|
|
11
|
+
read_fieldset(fields).each_with_object({}) do |rest_field, result|
|
|
12
|
+
next unless record.respond_to?(rest_field.name)
|
|
13
|
+
|
|
14
|
+
result[rest_field.name] = read_attribute(record, rest_field.name)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def readable_fields
|
|
19
|
+
field_names
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def read_attribute(record, field_name)
|
|
23
|
+
model = @definition.model
|
|
24
|
+
|
|
25
|
+
# Handle "belongs to" and "has one" associations
|
|
26
|
+
if Habarico::ReflectionHelper.is_active_record_class?(model)
|
|
27
|
+
reflection = model.reflect_on_association(field_name)
|
|
28
|
+
return record.send(reflection.foreign_key) if reflection && %i[belongs_to has_one].include?(reflection.macro)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Handle enum attribute
|
|
32
|
+
if model.respond_to?(:has_enum_attr?) && model.has_enum_attr?(field_name)
|
|
33
|
+
enum_obj = record.send("#{field_name}_obj")
|
|
34
|
+
return enum_obj ? enum_obj[:value] : nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Fallback to a standard model attribute
|
|
38
|
+
record.send(field_name)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def field_names
|
|
44
|
+
(@definition.fields + @definition.read_only_fields).uniq.map { |field| field_name_for(field) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def field_name_for(field)
|
|
48
|
+
if field.is_a?(Habarico::FieldReference)
|
|
49
|
+
field.name.to_s
|
|
50
|
+
elsif field.is_a?(::String) || field.is_a?(::Symbol)
|
|
51
|
+
field.to_s
|
|
52
|
+
elsif field.is_a?(::Hash)
|
|
53
|
+
field.keys.first.to_s
|
|
54
|
+
else
|
|
55
|
+
raise Eluvia::Errors::StandardError.new("Unsupported field type: #{field.class.name}")
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module Habarico
|
|
2
|
+
class ResourceService
|
|
3
|
+
include Singleton
|
|
4
|
+
|
|
5
|
+
def get_controller_class(resource_path)
|
|
6
|
+
return nil if resource_path.blank?
|
|
7
|
+
controller_class_name = ''
|
|
8
|
+
controller_class_name += "#{Habarico::Config.routing_scope.camelize}::" if Habarico::Config.routing_scope.present?
|
|
9
|
+
controller_class_name += resource_path.map { |part| part.to_s.camelize }.join('::') + 'Controller' # rubocop:disable Style/StringConcatenation -- would be less readable
|
|
10
|
+
controller_class_name.constantize
|
|
11
|
+
rescue NameError
|
|
12
|
+
raise Eluvia::Errors::NotFound.new("Controller class #{controller_class_name} not found for resource #{resource_path.join('.')}")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def get_definition(resource_path)
|
|
16
|
+
return nil if resource_path.blank?
|
|
17
|
+
get_controller_class(resource_path)&.definition
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def get_resource_path(resource_name)
|
|
21
|
+
return nil if resource_name.blank?
|
|
22
|
+
flatten_specs(Habarico::Config.resources).find { |resource_path| resource_path.last == resource_name.to_sym }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def get_resources_paths
|
|
26
|
+
flatten_specs(Habarico::Config.resources)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def find_resource_name_by_model_class_or_instance(model_class_or_instance)
|
|
30
|
+
return nil if model_class_or_instance.blank?
|
|
31
|
+
if Habarico::ReflectionHelper.is_active_record_class?(model_class_or_instance)
|
|
32
|
+
return find_resource_name_by_model_class(model_class_or_instance)
|
|
33
|
+
end
|
|
34
|
+
if Habarico::ReflectionHelper.is_active_record_instance?(model_class_or_instance)
|
|
35
|
+
return find_resource_name_by_model_class(model_class_or_instance.class)
|
|
36
|
+
end
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def find_resource_name_by_model_class(model_class)
|
|
41
|
+
return nil if model_class.blank?
|
|
42
|
+
get_resources_paths.each do |resource_path|
|
|
43
|
+
controller_class = get_controller_class(resource_path)
|
|
44
|
+
if controller_class && controller_class.definition.model == model_class # match model class
|
|
45
|
+
return resource_path.last # return resource name
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def flatten_specs(specs, namespaces = [])
|
|
54
|
+
result = []
|
|
55
|
+
specs.each do |resource_or_namespace|
|
|
56
|
+
if resource_or_namespace.is_a?(Hash)
|
|
57
|
+
resource_or_namespace.each do |namespace_name, specs_2|
|
|
58
|
+
result += flatten_specs(specs_2, namespaces + [namespace_name])
|
|
59
|
+
end
|
|
60
|
+
else
|
|
61
|
+
resource_name = resource_or_namespace
|
|
62
|
+
result << (namespaces + [resource_name])
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
result
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
module Habarico
|
|
2
|
+
class RoutingService
|
|
3
|
+
include Singleton
|
|
4
|
+
|
|
5
|
+
# Opt-in contrib modules register extra route blocks. They are drawn at the admin-site
|
|
6
|
+
# root - beside schema/search - which for a grouped resource config (e.g.
|
|
7
|
+
# `{ admin: [...] }`) means under the top-level group's path, and for a flat resource
|
|
8
|
+
# list means directly under `routing_url_prefix`. This mirrors the Python AdminSite,
|
|
9
|
+
# where user-preferences sits next to schema/search. See draw_extension_routes.
|
|
10
|
+
@extension_route_blocks = []
|
|
11
|
+
|
|
12
|
+
# Route blocks drawn *inside* every resource's own `resources ... do` block, where a feature
|
|
13
|
+
# can add member/collection routes to each resource (the aggregate endpoint does). Registered
|
|
14
|
+
# by a pro/enterprise gem; empty in a free-only install - see draw_resource_extension_routes.
|
|
15
|
+
@resource_extension_route_blocks = []
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
attr_reader :extension_route_blocks, :resource_extension_route_blocks
|
|
19
|
+
|
|
20
|
+
def register_route_block(&block)
|
|
21
|
+
extension_route_blocks << block
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Registers a block drawn inside each resource's route block. The block is instance_exec'd
|
|
25
|
+
# in the Rails mapper, so it may use `collection` / `member` exactly as core's own route
|
|
26
|
+
# helpers do, and receives the resource name.
|
|
27
|
+
def register_resource_route_block(&block)
|
|
28
|
+
resource_extension_route_blocks << block
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Draws the registered per-resource blocks. Called once per resource, before core's own
|
|
32
|
+
# routes: a collection route added here must not be shadowed by the member `GET /:id/schema`
|
|
33
|
+
# route, which has no `:id` constraint and would otherwise match it first.
|
|
34
|
+
def draw_resource_extension_routes(mapper, resource_name)
|
|
35
|
+
resource_extension_route_blocks.each do |block|
|
|
36
|
+
mapper.instance_exec(resource_name, &block)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Draws the registered extension blocks in the given routing mapper, at the admin-site
|
|
41
|
+
# root so they sit beside schema/search - matching the Python AdminSite. When the
|
|
42
|
+
# resources are wrapped in a top-level group the site root is that group's path, so
|
|
43
|
+
# re-open it here; for a flat resource list it is the url prefix itself. This is a
|
|
44
|
+
# path-only `scope` (not a `namespace`) on purpose: the contrib controllers live in the
|
|
45
|
+
# gem, not under the host's module, so no module prefix may be applied to them.
|
|
46
|
+
def draw_extension_routes(mapper)
|
|
47
|
+
top_group = Habarico::Config.resources.find { |spec| spec.is_a?(Hash) }&.keys&.first
|
|
48
|
+
return draw_extension_route_blocks(mapper) unless top_group
|
|
49
|
+
|
|
50
|
+
mapper.scope(top_group) { draw_extension_route_blocks(mapper) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
# The blocks are written against the routing mapper DSL, so they must be evaluated in
|
|
56
|
+
# the mapper's context (`scope` above only changes the mapper state, not `self`).
|
|
57
|
+
def draw_extension_route_blocks(mapper)
|
|
58
|
+
extension_route_blocks.each { |route_block| mapper.instance_exec(&route_block) }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def draw_routes
|
|
63
|
+
Rails.application.routes.draw do
|
|
64
|
+
def _draw_routes(specs)
|
|
65
|
+
specs.each do |resource_or_namespace|
|
|
66
|
+
if resource_or_namespace.is_a?(Hash)
|
|
67
|
+
resource_or_namespace.each do |namespace_name, specs_2|
|
|
68
|
+
namespace namespace_name do
|
|
69
|
+
_draw_routes(specs_2)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
else
|
|
73
|
+
get 'schema', to: '/habarico/global_schema#index'
|
|
74
|
+
_draw_resource_routes(resource_or_namespace)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def _draw_resource_routes(resource_name)
|
|
80
|
+
resources resource_name, only: %i[index show create update destroy] do
|
|
81
|
+
# Must be drawn before `_draw_schema_routes`: its member route `GET /:id/schema` has no `:id`
|
|
82
|
+
# constraint, so it would otherwise greedily match a contributed collection route too (with
|
|
83
|
+
# `:id` == "aggregate", say) and shadow it, since Rails resolves ambiguous matches by
|
|
84
|
+
# registration order.
|
|
85
|
+
Habarico::RoutingService.draw_resource_extension_routes(self, resource_name)
|
|
86
|
+
_draw_actions_routes(resource_name)
|
|
87
|
+
_draw_schema_routes
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def _draw_actions_routes(resource_name)
|
|
92
|
+
resource_path = Habarico::ResourceService.instance.get_resource_path(resource_name)
|
|
93
|
+
controller_class = Habarico::ResourceService.instance.get_controller_class(resource_path)
|
|
94
|
+
registered_actions = controller_class.registered_actions
|
|
95
|
+
|
|
96
|
+
# Member actions
|
|
97
|
+
member_actions = registered_actions.select { |action| action[:is_detail] }
|
|
98
|
+
unless member_actions.empty?
|
|
99
|
+
member do
|
|
100
|
+
member_actions.each do |action|
|
|
101
|
+
_draw_action_routes(action, is_member: true)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Collection actions
|
|
107
|
+
collection_actions = registered_actions.reject { |action| action[:is_detail] }
|
|
108
|
+
unless collection_actions.empty?
|
|
109
|
+
collection do
|
|
110
|
+
collection_actions.each do |action|
|
|
111
|
+
_draw_action_routes(action, is_member: false)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def _draw_action_routes(action, is_member: true)
|
|
118
|
+
if action[:method] == :get
|
|
119
|
+
get action[:name]
|
|
120
|
+
elsif action[:method] == :post
|
|
121
|
+
post action[:name]
|
|
122
|
+
elsif action[:method] == :put
|
|
123
|
+
put action[:name]
|
|
124
|
+
elsif action[:method] == :patch
|
|
125
|
+
patch action[:name]
|
|
126
|
+
elsif action[:method] == :delete
|
|
127
|
+
delete action[:name]
|
|
128
|
+
else
|
|
129
|
+
raise Eluvia::Errors::StandardError.new("Unsupported HTTP method #{action[:method]} for action #{action[:name]}")
|
|
130
|
+
end
|
|
131
|
+
get "#{action[:name]}/schema", to: "/habarico/action_schema##{is_member ? 'show' : 'index'}"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def _draw_schema_routes
|
|
135
|
+
collection do
|
|
136
|
+
get :schema, to: '/habarico/local_schema#index'
|
|
137
|
+
end
|
|
138
|
+
member do
|
|
139
|
+
get :schema, to: '/habarico/local_schema#show'
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
scope Habarico::Config.routing_url_prefix do
|
|
144
|
+
if Habarico::Config.routing_scope
|
|
145
|
+
scope module: Habarico::Config.routing_scope do
|
|
146
|
+
_draw_routes(Habarico::Config.resources)
|
|
147
|
+
end
|
|
148
|
+
else
|
|
149
|
+
_draw_routes(Habarico::Config.resources)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Opt-in contrib extension routes (e.g. user-preferences) - see
|
|
153
|
+
# RoutingService.draw_extension_routes for the placement rules.
|
|
154
|
+
Habarico::RoutingService.draw_extension_routes(self)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
end
|
|
160
|
+
end
|