linked_rails 0.0.3 → 0.0.4.pre.g0ec6e30ca

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 (180) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +21 -674
  3. data/app/controllers/linked_rails/actions/objects_controller.rb +9 -0
  4. data/app/controllers/linked_rails/bulk_controller.rb +107 -22
  5. data/app/controllers/linked_rails/enum_values_controller.rb +0 -42
  6. data/app/models/linked_rails/actions/item.rb +72 -55
  7. data/app/models/linked_rails/actions/list.rb +6 -31
  8. data/app/models/linked_rails/actions/object.rb +40 -0
  9. data/app/models/linked_rails/collection/configuration.rb +55 -0
  10. data/app/models/linked_rails/collection/filter.rb +1 -1
  11. data/app/models/linked_rails/collection/filter_field.rb +19 -2
  12. data/app/models/linked_rails/collection/filter_option.rb +1 -1
  13. data/app/models/linked_rails/collection/filterable.rb +6 -9
  14. data/app/models/linked_rails/collection/infinite.rb +113 -0
  15. data/app/models/linked_rails/collection/infinite_view.rb +1 -90
  16. data/app/models/linked_rails/collection/iri.rb +47 -43
  17. data/app/models/linked_rails/collection/iri_mapping.rb +15 -7
  18. data/app/models/linked_rails/collection/paginated.rb +46 -0
  19. data/app/models/linked_rails/collection/paginated_view.rb +1 -33
  20. data/app/models/linked_rails/collection/sortable.rb +1 -9
  21. data/app/models/linked_rails/collection/sorting.rb +1 -1
  22. data/app/models/linked_rails/collection/view.rb +53 -15
  23. data/app/models/linked_rails/collection.rb +53 -85
  24. data/app/models/linked_rails/creative_work.rb +1 -1
  25. data/app/models/linked_rails/entry_point.rb +8 -5
  26. data/app/models/linked_rails/enum_value.rb +40 -2
  27. data/app/models/linked_rails/form/field/association_input.rb +7 -1
  28. data/app/models/linked_rails/form/field/file_input.rb +5 -0
  29. data/app/models/linked_rails/form/field/resource_field.rb +2 -0
  30. data/{lib/linked_rails/enhancements/indexable/model.rb → app/models/linked_rails/form/field/url_input.rb} +3 -3
  31. data/app/models/linked_rails/form/field.rb +37 -13
  32. data/app/models/linked_rails/form/field_factory.rb +49 -25
  33. data/app/models/linked_rails/form/group.rb +4 -6
  34. data/app/models/linked_rails/form/page.rb +8 -4
  35. data/app/models/linked_rails/form.rb +17 -21
  36. data/app/models/linked_rails/manifest.rb +102 -22
  37. data/app/models/linked_rails/menus/item.rb +11 -14
  38. data/app/models/linked_rails/menus/list.rb +30 -10
  39. data/app/models/linked_rails/ontology/base.rb +3 -1
  40. data/app/models/linked_rails/ontology/class.rb +3 -3
  41. data/app/models/linked_rails/ontology.rb +6 -5
  42. data/app/models/linked_rails/property_query.rb +2 -0
  43. data/app/models/linked_rails/sequence.rb +4 -13
  44. data/app/models/linked_rails/shacl/property_shape.rb +1 -1
  45. data/app/models/linked_rails/web_page.rb +0 -4
  46. data/app/models/linked_rails/web_site.rb +0 -4
  47. data/app/models/linked_rails/widget.rb +4 -11
  48. data/app/policies/linked_rails/actions/object_policy.rb +11 -0
  49. data/app/policies/linked_rails/collection_policy.rb +3 -2
  50. data/app/policies/linked_rails/form_policy.rb +13 -0
  51. data/app/policies/linked_rails/ontology_policy.rb +13 -0
  52. data/app/serializers/linked_rails/actions/item_serializer.rb +5 -5
  53. data/app/serializers/linked_rails/collection/filter_field_serializer.rb +3 -2
  54. data/app/serializers/linked_rails/collection/filter_option_serializer.rb +1 -1
  55. data/app/serializers/linked_rails/collection/filter_serializer.rb +1 -1
  56. data/app/serializers/linked_rails/collection/sorting_serializer.rb +1 -1
  57. data/app/serializers/linked_rails/collection/view_serializer.rb +4 -3
  58. data/app/serializers/linked_rails/collection_serializer.rb +8 -7
  59. data/app/serializers/linked_rails/condition_serializer.rb +3 -3
  60. data/app/serializers/linked_rails/entry_point_serializer.rb +3 -3
  61. data/app/serializers/linked_rails/enum_value_serializer.rb +1 -0
  62. data/app/serializers/linked_rails/form/field/association_input_serializer.rb +1 -0
  63. data/app/serializers/linked_rails/form/field/file_input_serializer.rb +11 -0
  64. data/app/serializers/linked_rails/form/field_serializer.rb +3 -1
  65. data/app/serializers/linked_rails/form/group_serializer.rb +1 -1
  66. data/app/serializers/linked_rails/form/page_serializer.rb +1 -1
  67. data/app/serializers/linked_rails/menus/item_serializer.rb +3 -3
  68. data/app/serializers/linked_rails/menus/list_serializer.rb +1 -1
  69. data/app/serializers/linked_rails/ontology_serializer.rb +2 -2
  70. data/app/serializers/linked_rails/property_query_serializer.rb +7 -0
  71. data/app/serializers/linked_rails/sequence_serializer.rb +2 -5
  72. data/app/serializers/linked_rails/shacl/node_shape_serializer.rb +1 -1
  73. data/app/serializers/linked_rails/shacl/property_shape_serializer.rb +1 -1
  74. data/app/serializers/linked_rails/shacl/shape_serializer.rb +5 -5
  75. data/app/serializers/linked_rails/web_page_serializer.rb +3 -3
  76. data/app/serializers/linked_rails/web_site_serializer.rb +1 -1
  77. data/app/serializers/linked_rails/widget_serializer.rb +3 -3
  78. data/app/workers/linked_rails/invalidation_stream_worker.rb +16 -0
  79. data/lib/generators/linked_rails/install/install_generator.rb +5 -8
  80. data/lib/generators/linked_rails/install/templates/README +2 -0
  81. data/lib/generators/linked_rails/install/templates/app_menu_list.rb +36 -7
  82. data/lib/generators/linked_rails/install/templates/application_menu_list.rb +40 -1
  83. data/lib/generators/linked_rails/install/templates/initializer.rb +1 -2
  84. data/lib/generators/linked_rails/install/templates/locales.yml +16 -0
  85. data/lib/generators/linked_rails/install/templates/rdf_serializers_initializer.rb +1 -1
  86. data/lib/generators/linked_rails/install/templates/vocab.rb +1 -0
  87. data/lib/generators/linked_rails/install/templates/vocab.yml +2 -2
  88. data/lib/generators/linked_rails/model/model_generator.rb +0 -1
  89. data/lib/generators/linked_rails/model/templates/controller.rb.tt +5 -1
  90. data/lib/generators/linked_rails/model/templates/form.rb.tt +3 -0
  91. data/lib/generators/linked_rails/model/templates/menu_list.rb.tt +15 -0
  92. data/lib/generators/linked_rails/model/templates/policy.rb.tt +13 -0
  93. data/lib/generators/linked_rails/model/templates/serializer.rb.tt +5 -1
  94. data/lib/linked_rails/active_response/controller/collections.rb +1 -1
  95. data/lib/linked_rails/active_response/controller/crud_defaults.rb +4 -4
  96. data/lib/linked_rails/active_response/controller/params.rb +10 -10
  97. data/lib/linked_rails/active_response/controller.rb +8 -18
  98. data/lib/linked_rails/active_response/responders/rdf.rb +19 -10
  99. data/lib/linked_rails/callable_variable.rb +1 -1
  100. data/lib/linked_rails/collection_params_parser.rb +93 -0
  101. data/lib/linked_rails/controller/actionable.rb +121 -0
  102. data/lib/linked_rails/controller/authorization.rb +6 -0
  103. data/lib/linked_rails/controller/default_actions/create.rb +52 -0
  104. data/lib/linked_rails/controller/default_actions/destroy.rb +42 -0
  105. data/lib/linked_rails/controller/default_actions/update.rb +43 -0
  106. data/lib/linked_rails/controller/delta.rb +58 -0
  107. data/lib/linked_rails/controller/error_handling.rb +12 -7
  108. data/lib/linked_rails/controller/rendering.rb +48 -0
  109. data/lib/linked_rails/controller.rb +24 -4
  110. data/lib/linked_rails/enhancements/creatable/controller.rb +1 -1
  111. data/lib/linked_rails/enhancements/destroyable/controller.rb +1 -1
  112. data/lib/linked_rails/enhancements/updatable/controller.rb +1 -1
  113. data/lib/linked_rails/enhancements.rb +0 -16
  114. data/lib/linked_rails/errors/forbidden.rb +37 -0
  115. data/lib/linked_rails/errors.rb +3 -0
  116. data/lib/linked_rails/helpers/delta_helper.rb +28 -57
  117. data/lib/linked_rails/helpers/ontola_actions_helper.rb +2 -2
  118. data/lib/linked_rails/helpers/resource_helper.rb +14 -2
  119. data/lib/linked_rails/iri_mapper.rb +18 -40
  120. data/lib/linked_rails/middleware/error_handling.rb +51 -0
  121. data/lib/linked_rails/middleware/linked_data_params.rb +30 -151
  122. data/lib/linked_rails/model/actionable.rb +68 -0
  123. data/lib/linked_rails/model/cacheable.rb +45 -0
  124. data/lib/linked_rails/model/collections.rb +203 -39
  125. data/lib/linked_rails/model/dirty.rb +6 -18
  126. data/lib/linked_rails/model/enhancements.rb +1 -6
  127. data/lib/linked_rails/model/filtering.rb +4 -6
  128. data/lib/linked_rails/model/indexable.rb +6 -16
  129. data/lib/linked_rails/model/iri.rb +29 -19
  130. data/lib/linked_rails/model/iri_mapping.rb +37 -8
  131. data/lib/linked_rails/model/menuable.rb +28 -0
  132. data/lib/linked_rails/model/serialization.rb +2 -15
  133. data/lib/linked_rails/model/singularable.rb +57 -0
  134. data/lib/linked_rails/model/sorting.rb +0 -5
  135. data/lib/linked_rails/model/tables.rb +26 -0
  136. data/lib/linked_rails/model.rb +18 -7
  137. data/lib/linked_rails/params_parser.rb +140 -53
  138. data/lib/linked_rails/policy/attribute_conditions.rb +2 -2
  139. data/lib/linked_rails/policy.rb +44 -46
  140. data/lib/linked_rails/railtie.rb +11 -0
  141. data/lib/linked_rails/rdf_error.rb +2 -2
  142. data/lib/linked_rails/renderers.rb +1 -0
  143. data/lib/linked_rails/routes.rb +38 -22
  144. data/lib/linked_rails/serializer/actionable.rb +27 -0
  145. data/lib/linked_rails/serializer/menuable.rb +31 -0
  146. data/lib/linked_rails/serializer/singularable.rb +26 -0
  147. data/lib/linked_rails/serializer.rb +35 -12
  148. data/lib/linked_rails/storage.rb +32 -0
  149. data/lib/linked_rails/test_methods.rb +114 -0
  150. data/lib/linked_rails/translate.rb +31 -9
  151. data/lib/linked_rails/types/iri_type.rb +37 -0
  152. data/lib/linked_rails/uri_template.rb +30 -0
  153. data/lib/linked_rails/url.rb +11 -0
  154. data/lib/linked_rails/version.rb +1 -1
  155. data/lib/linked_rails/vocab.rb +9 -0
  156. data/lib/linked_rails.rb +40 -14
  157. data/lib/rails/welcome_controller.rb +3 -3
  158. data/lib/rdf/list.rb +9 -0
  159. data/lib/rdf/query_fix.rb +15 -0
  160. metadata +78 -33
  161. data/app/models/linked_rails/actions/default_actions/create.rb +0 -60
  162. data/app/models/linked_rails/actions/default_actions/destroy.rb +0 -45
  163. data/app/models/linked_rails/actions/default_actions/update.rb +0 -50
  164. data/app/models/linked_rails/actions/default_actions.rb +0 -17
  165. data/lib/generators/linked_rails/install/templates/application_action_list.rb +0 -3
  166. data/lib/generators/linked_rails/model/templates/action_list.rb.tt +0 -6
  167. data/lib/linked_rails/enhancements/actionable/model.rb +0 -71
  168. data/lib/linked_rails/enhancements/actionable/serializer.rb +0 -25
  169. data/lib/linked_rails/enhancements/creatable/action.rb +0 -15
  170. data/lib/linked_rails/enhancements/destroyable/action.rb +0 -15
  171. data/lib/linked_rails/enhancements/destroyable/routing.rb +0 -19
  172. data/lib/linked_rails/enhancements/menuable/model.rb +0 -36
  173. data/lib/linked_rails/enhancements/menuable/serializer.rb +0 -33
  174. data/lib/linked_rails/enhancements/route_concerns.rb +0 -56
  175. data/lib/linked_rails/enhancements/singularable/controller.rb +0 -43
  176. data/lib/linked_rails/enhancements/singularable/model.rb +0 -47
  177. data/lib/linked_rails/enhancements/singularable/serializer.rb +0 -28
  178. data/lib/linked_rails/enhancements/tableable/model.rb +0 -28
  179. data/lib/linked_rails/enhancements/updatable/action.rb +0 -15
  180. data/lib/linked_rails/enhancements/updatable/routing.rb +0 -20
@@ -3,10 +3,17 @@
3
3
  module LinkedRails
4
4
  class Form
5
5
  class FieldFactory # rubocop:disable Metrics/ClassLength
6
+ DATABASE_ERRORS = [
7
+ 'PG::ConnectionBad'.safe_constantize,
8
+ ActiveRecord::StatementInvalid,
9
+ ActiveRecord::ConnectionNotEstablished
10
+ ].compact.freeze
6
11
  MAX_STR_LEN = 255
7
12
  VALIDATOR_SELECTORS = [
8
13
  [:min_length, ActiveModel::Validations::LengthValidator, :minimum],
14
+ [:min_inclusive, ActiveModel::Validations::NumericalityValidator, :greater_than_or_equal_to],
9
15
  [:max_length, ActiveModel::Validations::LengthValidator, :maximum],
16
+ [:max_inclusive, ActiveModel::Validations::NumericalityValidator, :less_than_or_equal_to],
10
17
  [:pattern, ActiveModel::Validations::FormatValidator, :with],
11
18
  [:presence, ActiveModel::Validations::PresenceValidator, nil],
12
19
  [:sh_in, ActiveModel::Validations::InclusionValidator, :in]
@@ -15,7 +22,8 @@ module LinkedRails
15
22
 
16
23
  attr_accessor :field_options, :form, :key
17
24
 
18
- delegate :form_options_iri,
25
+ delegate :abstract_form,
26
+ :form_options_iri,
19
27
  :model_class,
20
28
  :model_policy!,
21
29
  :serializer_attributes,
@@ -24,29 +32,31 @@ module LinkedRails
24
32
  to: :form
25
33
 
26
34
  def condition_or_field
27
- @condition_or_field ||= model_policy!.condition_for(
35
+ return @condition_or_field if instance_variable_defined?(:@condition_or_field)
36
+
37
+ alternatives = node_shapes_for(
28
38
  key,
29
- field,
30
39
  property: field_options[:if] || [],
31
40
  sh_not: field_options[:unless] || []
32
41
  )
42
+ @condition_or_field =
43
+ if alternatives.count == 1
44
+ Condition.new(shape: alternatives.first, pass: field)
45
+ elsif alternatives.count.positive?
46
+ Condition.new(shape: SHACL::NodeShape.new(or: alternatives), pass: field)
47
+ else
48
+ field
49
+ end
33
50
  end
34
51
 
35
52
  private
36
53
 
37
54
  def attr_column(name)
38
- column_model =
39
- if model_class.is_delegated_attribute?(name)
40
- model_class.class_for_delegated_attribute(name)
41
- else
42
- model_class
43
- end
44
- column_model.column_for_attribute(name)
55
+ model_class.column_for_attribute(name)
45
56
  end
46
57
 
47
58
  def attr_to_datatype # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
48
- name = serializer_attribute&.key&.to_s || key.to_s
49
- case attribute_type(name)
59
+ case attribute_type
50
60
  when :string, :text
51
61
  Vocab.xsd.string
52
62
  when :integer
@@ -58,22 +68,26 @@ module LinkedRails
58
68
  when :boolean
59
69
  Vocab.xsd.boolean
60
70
  when :decimal
61
- decimal_data_type(name)
71
+ decimal_data_type(attribute_name)
62
72
  when :file
63
73
  Vocab.ll[:blob]
64
74
  else
65
- Vocab.xsd.string if model_class.try(:defined_enums)&.key?(name)
75
+ Vocab.xsd.string if model_class.try(:defined_enums)&.key?(attribute_name)
66
76
  end
67
77
  end
68
78
 
69
- def attribute_type(name)
70
- model_class.try(:attribute_types).try(:[], name)&.type
71
- rescue PG::ConnectionBad, ActiveRecord::StatementInvalid, ActiveRecord::ConnectionNotEstablished
79
+ def attribute_name
80
+ serializer_attribute&.key&.to_s || key.to_s
81
+ end
82
+
83
+ def attribute_type
84
+ model_class.try(:attribute_types).try(:[], attribute_name)&.type
85
+ rescue *DATABASE_ERRORS
72
86
  :string
73
87
  end
74
88
 
75
89
  def attribute_validators
76
- @attribute_validators ||= model_class.validators.select { |v| v.attributes.include?(model_attribute) }
90
+ @attribute_validators ||= model_class&.validators&.select { |v| v.attributes.include?(model_attribute) }
77
91
  end
78
92
 
79
93
  def datatype
@@ -143,7 +157,7 @@ module LinkedRails
143
157
  when Vocab.ontola['datatype/postalRange']
144
158
  return Form::Field::PostalRangeInput
145
159
  else
146
- max_length && max_length > MAX_STR_LEN ? Form::Field::TextAreaInput : Form::Field::TextInput
160
+ attribute_type == :text ? Form::Field::TextAreaInput : Form::Field::TextInput
147
161
  end
148
162
  end
149
163
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
@@ -168,6 +182,15 @@ module LinkedRails
168
182
  @model_attribute ||= (model_class.try(:attribute_alias, key) || key).to_sym
169
183
  end
170
184
 
185
+ def node_shapes_for(attr, property: [], sh_not: [])
186
+ alternatives = abstract_form ? [] : model_policy!.condition_alternatives(attr, field.permission_required?)
187
+ alternatives = [[]] if alternatives.empty? && (property.any? || sh_not.any?)
188
+
189
+ alternatives.map do |props|
190
+ SHACL::NodeShape.new(property: props + property, sh_not: sh_not)
191
+ end
192
+ end
193
+
171
194
  def normalized_key(key)
172
195
  return key.to_s[0...-3].to_sym if key.to_s.ends_with?('_id')
173
196
  return key.to_s[0...-4].pluralize.to_sym if key.to_s.ends_with?('_ids')
@@ -197,14 +220,15 @@ module LinkedRails
197
220
  def validator(klass, option_key)
198
221
  matched_validator = validator_by_class(klass)
199
222
 
200
- option_key ? matched_validator&.options.try(:[], option_key) : matched_validator.present?
223
+ value = option_key ? matched_validator&.options.try(:[], option_key) : matched_validator.present?
224
+
225
+ value unless value.respond_to?(:call)
201
226
  end
202
227
 
203
228
  def validator_by_class(klass)
204
- matched_validator = attribute_validators&.detect { |v| v.is_a?(klass) }
205
- return if matched_validator.blank?
206
-
207
- matched_validator if matched_validator.options[:if].blank? && matched_validator.options[:unless].blank?
229
+ attribute_validators&.detect do |validator|
230
+ validator.is_a?(klass) && validator.options[:if].blank? && validator.options[:unless].blank?
231
+ end
208
232
  end
209
233
 
210
234
  def validators
@@ -212,7 +236,7 @@ module LinkedRails
212
236
  VALIDATOR_SELECTORS.map do |key, klass, option_key|
213
237
  [key, validator(klass, option_key)]
214
238
  end
215
- ]
239
+ ].compact
216
240
  end
217
241
  end
218
242
  end
@@ -6,19 +6,17 @@ module LinkedRails
6
6
  attr_accessor :fields, :collapsible, :hidden, :footer
7
7
  attr_writer :description, :key, :label
8
8
 
9
- def initialize(attrs = {})
10
- super(attrs)
9
+ def initialize(**attrs)
10
+ super(**attrs)
11
11
  self.fields = []
12
12
  end
13
13
 
14
14
  def description
15
- @description = @description.call if @description.respond_to?(:call)
16
- @description
15
+ @description.respond_to?(:call) ? @description.call : @description
17
16
  end
18
17
 
19
18
  def label
20
- @label = @label.call if @label.respond_to?(:call)
21
- @label
19
+ @label.respond_to?(:call) ? @label.call : @label
22
20
  end
23
21
 
24
22
  def rdf_type
@@ -6,13 +6,13 @@ module LinkedRails
6
6
  attr_accessor :groups, :label, :description
7
7
  attr_writer :key
8
8
 
9
- def initialize(attrs = {})
10
- super(attrs)
9
+ def initialize(**attrs)
10
+ super(**attrs)
11
11
  self.groups = []
12
12
  end
13
13
 
14
- def add_group(opts)
15
- group = Form::Group.new(opts)
14
+ def add_group(**opts)
15
+ group = Form::Group.new(**opts)
16
16
  groups << group
17
17
  group
18
18
  end
@@ -21,6 +21,10 @@ module LinkedRails
21
21
  @footer_group ||= add_group(collapsible: false, footer: true, key: :footer)
22
22
  end
23
23
 
24
+ def footer_group!
25
+ @footer_group
26
+ end
27
+
24
28
  class << self
25
29
  def iri
26
30
  Vocab.form[:Page]
@@ -5,10 +5,11 @@ require 'pundit'
5
5
  module LinkedRails
6
6
  class Form # rubocop:disable Metrics/ClassLength
7
7
  include LinkedRails::Model
8
+ include LinkedRails::Model::Cacheable
8
9
 
9
- class_attribute :pages, :model_class
10
+ class_attribute :abstract_form, :pages, :model_class
10
11
 
11
- def root_relative_iri(_opts = {})
12
+ def root_relative_iri(**_opts)
12
13
  self.class.form_iri_path
13
14
  end
14
15
 
@@ -63,10 +64,6 @@ module LinkedRails
63
64
  @serializer_reflections ||= serializer_class&.relationships_to_serialize || {}
64
65
  end
65
66
 
66
- def preview_includes
67
- [pages: {groups: [fields: [:fail, :pass, shape: [:property, nested_shapes: :property]]]}]
68
- end
69
-
70
67
  private
71
68
 
72
69
  def current_group
@@ -85,7 +82,7 @@ module LinkedRails
85
82
  @default_page ||= page(:default)
86
83
  end
87
84
 
88
- def field(key, opts = {})
85
+ def field(key, **opts)
89
86
  current_group.fields << Form::FieldFactory.new(
90
87
  field_options: opts,
91
88
  form: self,
@@ -94,9 +91,10 @@ module LinkedRails
94
91
  end
95
92
 
96
93
  def find_form_class(params)
97
- requested_class = [params[:module], "#{params[:id]&.singularize}_forms"].compact.join('/').classify
94
+ form_name = [params[:module], "#{params[:id]&.singularize}_forms"].compact.join('/').classify
95
+ requested_class = form_name.safe_constantize
98
96
 
99
- LinkedRails::Form.descendants.detect { |klass| klass.to_s == requested_class }
97
+ requested_class if requested_class && requested_class < LinkedRails::Form
100
98
  end
101
99
 
102
100
  def footer
@@ -108,10 +106,10 @@ module LinkedRails
108
106
  current_page.footer_group
109
107
  end
110
108
 
111
- def group(key, opts = {})
109
+ def group(key, **opts)
112
110
  opts[:collapsible] = true unless opts.key?(:collapsible)
113
111
  opts[:key] = key
114
- group = current_page.add_group(opts)
112
+ group = current_page.add_group(**opts)
115
113
  @current_group = group
116
114
 
117
115
  yield if block_given?
@@ -120,26 +118,24 @@ module LinkedRails
120
118
  group
121
119
  end
122
120
 
123
- # rubocop:disable Naming/PredicateName
124
- def has_many(key, opts = {})
121
+ def has_many(key, **opts)
125
122
  opts[:input_field] = Form::Field::AssociationInput
126
123
  opts[:max_count] = 99
127
- field(key, opts)
124
+ field(key, **opts)
128
125
  end
129
126
 
130
- def has_one(key, opts = {})
127
+ def has_one(key, **opts)
131
128
  opts[:input_field] = Form::Field::AssociationInput
132
129
  opts[:max_count] = 1
133
- field(key, opts)
130
+ field(key, **opts)
134
131
  end
135
- # rubocop:enable Naming/PredicateName
136
132
 
137
133
  def hidden(&block)
138
134
  group(:hidden, collapsible: false, hidden: true, &block)
139
135
  end
140
136
 
141
- def page(key, opts = {})
142
- page = Form::Page.new(opts.merge(key: key))
137
+ def page(key, **opts)
138
+ page = Form::Page.new(**opts.merge(key: key))
143
139
  @current_page = page
144
140
  pages << @current_page
145
141
  yield if block_given?
@@ -147,9 +143,9 @@ module LinkedRails
147
143
  page
148
144
  end
149
145
 
150
- def resource(key, opts = {})
146
+ def resource(key, **opts)
151
147
  opts[:input_field] = Form::Field::ResourceField
152
- field(key, opts)
148
+ field(key, **opts)
153
149
  end
154
150
  end
155
151
  end
@@ -5,6 +5,27 @@ module LinkedRails
5
5
  include ActiveModel::Model
6
6
  include LinkedRails::Model
7
7
 
8
+ def save
9
+ Storage.hset(
10
+ :persistent,
11
+ :manifest,
12
+ URL.as_href(LinkedRails.iri) => web_manifest.to_json
13
+ )
14
+ end
15
+
16
+ def web_manifest
17
+ web_manifest_base.merge(
18
+ ontola: web_manifest_ontola_section,
19
+ serviceworker: web_manifest_sw_section
20
+ )
21
+ end
22
+
23
+ private
24
+
25
+ def allowed_external_sources
26
+ []
27
+ end
28
+
8
29
  def app_name
9
30
  Rails.application.railtie_name.chomp('_application').humanize
10
31
  end
@@ -17,8 +38,27 @@ module LinkedRails
17
38
  '#eef0f2'
18
39
  end
19
40
 
41
+ def blob_preview_iri
42
+ return unless ActiveStorage::Blob.service.present?
43
+
44
+ "#{LinkedRails.iri(path: 'rails/active_storage/blobs/redirect')}/{signed_id}/preview"
45
+ end
46
+
47
+ def blob_upload_iri
48
+ return unless ActiveStorage::Blob.service.present?
49
+
50
+ LinkedRails.iri(path: 'rails/active_storage/direct_uploads')
51
+ end
52
+
20
53
  def css_class; end
21
54
 
55
+ def csp_entries
56
+ {
57
+ connectSrc: [ActiveStorage::Blob.service.try(:bucket)&.url].compact,
58
+ scriptSrc: [ActiveStorage::Blob.service.try(:bucket)&.url].compact
59
+ }
60
+ end
61
+
22
62
  def header_background
23
63
  :primary
24
64
  end
@@ -27,17 +67,16 @@ module LinkedRails
27
67
  :white
28
68
  end
29
69
 
30
- def preload_iris
31
- [
32
- scope,
33
- LinkedRails.iri(path: 'ns/core').to_s,
34
- LinkedRails.iri(path: 'c_a').to_s,
35
- LinkedRails.iri(path: 'menus').to_s
36
- ]
70
+ def icons
71
+ []
72
+ end
73
+
74
+ def lang
75
+ :nl
37
76
  end
38
77
 
39
78
  def scope
40
- @scope ||= LinkedRails.iri.to_s
79
+ LinkedRails.iri.to_s
41
80
  end
42
81
 
43
82
  def site_theme_color
@@ -52,51 +91,92 @@ module LinkedRails
52
91
  app_name
53
92
  end
54
93
 
94
+ def start_url
95
+ scope == '/' ? scope : "#{scope}/"
96
+ end
97
+
55
98
  def theme; end
56
99
 
57
100
  def theme_options
58
101
  {}
59
102
  end
60
103
 
61
- def web_manifest
62
- web_manifest_base.merge(
63
- ontola: web_manifest_ontola_section,
64
- serviceworker: web_manifest_sw_section
65
- )
66
- end
67
-
68
104
  def web_manifest_base # rubocop:disable Metrics/MethodLength
69
105
  {
70
106
  background_color: background_color,
71
107
  dir: :rtl,
72
108
  display: :standalone,
73
- lang: :nl,
109
+ icons: icons,
110
+ lang: lang,
74
111
  name: app_name,
75
112
  scope: scope,
76
113
  short_name: app_name,
77
- start_url: scope,
114
+ start_url: start_url,
78
115
  theme_color: site_theme_color
79
116
  }
80
117
  end
81
118
 
82
- def web_manifest_ontola_section
119
+ def web_manifest_ontola_section # rubocop:disable Metrics/MethodLength
83
120
  {
84
- css_class: css_class,
121
+ allowed_external_sources: allowed_external_sources,
122
+ blob_preview_iri: blob_preview_iri,
123
+ blob_upload_iri: blob_upload_iri,
124
+ csp: csp_entries,
85
125
  header_background: header_background,
86
126
  header_text: header_text,
87
- preload: preload_iris,
127
+ preconnect: preconnect,
88
128
  primary_color: site_theme_color,
89
129
  secondary_color: site_secondary_color,
130
+ styled_headers: styled_headers,
90
131
  theme: theme,
91
- theme_options: theme_options.to_query
132
+ theme_options: theme_options.to_query,
133
+ tracking: tracking,
134
+ website_iri: LinkedRails.iri.to_s,
135
+ websocket_path: websocket_path
92
136
  }
93
137
  end
94
138
 
95
139
  def web_manifest_sw_section
96
140
  {
97
- src: "#{scope}/sw.js?manifestLocation=#{Rack::Utils.escape("#{scope}/manifest.json")}",
141
+ src: "#{scope.chomp('/')}/sw.js",
98
142
  scope: scope
99
143
  }
100
144
  end
145
+
146
+ def websocket_path
147
+ Rails.application.config.try(:action_cable).try(:mount_path).try(:[], 1..-1)
148
+ end
149
+
150
+ def preconnect
151
+ []
152
+ end
153
+
154
+ def styled_headers
155
+ false
156
+ end
157
+
158
+ def tracking
159
+ []
160
+ end
161
+
162
+ class << self
163
+ def destroy(iri)
164
+ Storage.hdel(:persistent, :manifest, URL.as_href(iri))
165
+ end
166
+
167
+ def move(from, to)
168
+ Storage.hset(
169
+ :persistent,
170
+ :redirect_prefix,
171
+ URL.as_href(from) => URL.as_href(to)
172
+ )
173
+
174
+ data = Storage.hget(:persistent, :manifest, URL.as_href(from))
175
+
176
+ Storage.hset(:persistent, :manifest, URL.as_href(to), data) if data
177
+
178
+ destroy(from)
179
+ end
180
+ end
101
181
  end
102
182
  end
@@ -12,7 +12,7 @@ module LinkedRails
12
12
  delegate :iri_opts, to: :parent
13
13
 
14
14
  alias id iri
15
- %i[action href image label menus iri_base].each do |method|
15
+ %i[action href image label iri_base].each do |method|
16
16
  callable_variable(method, instance: :parent)
17
17
  end
18
18
 
@@ -26,7 +26,7 @@ module LinkedRails
26
26
  def iri_template
27
27
  return parent.send(:iri_template) unless parent.is_a?(LinkedRails::Menus::List)
28
28
 
29
- return URITemplate.new("#{iri_base}{/tag}{#fragment}") if iri_base
29
+ return LinkedRails::URITemplate.new("#{iri_base}{/tag}{#fragment}") if iri_base
30
30
 
31
31
  iri_template_expand_path(parent.send(:iri_template), '{/tag}')
32
32
  end
@@ -36,7 +36,7 @@ module LinkedRails
36
36
 
37
37
  @menu_sequence ||=
38
38
  LinkedRails::Sequence.new(
39
- -> { menus&.compact&.each { |menu| menu.parent = self } },
39
+ -> { menus },
40
40
  id: menu_sequence_iri,
41
41
  parent: self,
42
42
  scope: false
@@ -52,6 +52,12 @@ module LinkedRails
52
52
  sequence_iri
53
53
  end
54
54
 
55
+ def menus
56
+ return @menus unless @menus.respond_to?(:call)
57
+
58
+ @menus = parent.instance_exec(&@menus).compact.each { |menu| menu.parent = self }
59
+ end
60
+
55
61
  def rdf_type
56
62
  type || Vocab.ontola[:MenuItem]
57
63
  end
@@ -67,24 +73,15 @@ module LinkedRails
67
73
  end
68
74
 
69
75
  class << self
70
- def base_includes
71
- [action: :target]
72
- end
73
-
74
76
  def preview_includes
75
- base_includes + [
76
- menu_sequence: [
77
- members: base_includes +
78
- [menu_sequence: [members: base_includes]]
79
- ]
80
- ]
77
+ [menu_sequence: [members: [menu_sequence: :members]]]
81
78
  end
82
79
 
83
80
  def requested_index_resource(params, user_context)
84
81
  parent = parent_from_params(params, user_context)
85
82
  return if parent.blank?
86
83
 
87
- parent.menu_sequence
84
+ parent.try(:menu_sequence)
88
85
  end
89
86
  end
90
87
  end
@@ -20,21 +20,28 @@ module LinkedRails
20
20
  end
21
21
 
22
22
  def iri_opts
23
- resource&.iri_opts || {}
23
+ return {} if resource.blank?
24
+
25
+ resource.try(:singular_resource?) ? resource.singular_iri_opts : resource.iri_opts
24
26
  end
25
27
 
26
28
  def menus
27
- @menus ||= available_menus.map(&method(:menu_item))
29
+ @menus ||= available_menus.map(&method(:memoized_menu_item))
28
30
  end
29
31
 
30
32
  def menu(tag)
31
- menu_item(tag, available_menus[tag].dup) if available_menus.key?(tag)
33
+ memoized_menu_item(tag, available_menus[tag].dup) if available_menus.key?(tag)
32
34
  end
33
35
 
34
36
  private
35
37
 
36
38
  def default_label(tag, options)
37
- I18n.t("menus.#{resource&.class&.name&.tableize}.#{tag}", options[:label_params])
39
+ I18n.t("menus.#{resource&.class&.name&.tableize}.#{tag}", **options[:label_params])
40
+ end
41
+
42
+ def memoized_menu_item(tag, options)
43
+ @memoized_menu_item ||= {}
44
+ @memoized_menu_item[tag] ||= menu_item(tag, options)
38
45
  end
39
46
 
40
47
  def menu_available?(_tag, options)
@@ -42,16 +49,20 @@ module LinkedRails
42
49
  end
43
50
 
44
51
  def menu_item(tag, options) # rubocop:disable Metrics/AbcSize
45
- if options[:policy].present?
46
- return unless resource_policy(options[:policy_resource]).send(options[:policy], *options[:policy_arguments])
47
- end
52
+ return unless show_menu_item?(tag, options)
53
+
48
54
  options[:label_params] ||= {}
49
55
  options[:label_params][:default] ||= ["menus.default.#{tag}".to_sym, tag.to_s.capitalize]
50
56
  options[:label] ||= default_label(tag, options)
57
+ options[:action] = ontola_dialog_action(options[:href]) if options.delete(:dialog)
51
58
  options.except!(:policy_resource, :policy, :policy_arguments, :label_params)
52
59
  LinkedRails.menus_item_class.new(resource: resource, tag: tag, parent: self, **options)
53
60
  end
54
61
 
62
+ def policy_verdict(policy, options)
63
+ policy.send(options[:policy], *options[:policy_arguments])
64
+ end
65
+
55
66
  def resource_policy(policy_resource)
56
67
  policy_resource ||= resource
57
68
  policy_resource = instance_exec(&policy_resource) if policy_resource.respond_to?(:call)
@@ -61,8 +72,18 @@ module LinkedRails
61
72
  @resource_policy[policy_resource] ||= Pundit.policy(user_context, policy_resource)
62
73
  end
63
74
 
75
+ def show_menu_item?(_tag, options)
76
+ return true if options[:policy].blank?
77
+
78
+ policy = resource_policy(options[:policy_resource])
79
+
80
+ policy_verdict(policy, options)
81
+ end
82
+
64
83
  def iri_template
65
- @iri_template ||= iri_template_expand_path(resource.send(:iri_template), '/menus')
84
+ base_template = resource.send(resource.try(:singular_resource?) ? :singular_iri_template : :iri_template)
85
+
86
+ @iri_template ||= iri_template_expand_path(base_template, '/menus')
66
87
  end
67
88
 
68
89
  class << self
@@ -88,7 +109,7 @@ module LinkedRails
88
109
  _defined_menus || {}
89
110
  end
90
111
 
91
- def has_menu(tag, opts = {}) # rubocop:disable Naming/PredicateName
112
+ def has_menu(tag, **opts)
92
113
  defined_menus[tag] = opts
93
114
  end
94
115
 
@@ -100,7 +121,6 @@ module LinkedRails
100
121
  LinkedRails::Sequence.new(
101
122
  menu_list.menus,
102
123
  id: menu_list.iri,
103
- member_includes: Item.preview_includes,
104
124
  scope: false
105
125
  )
106
126
  end
@@ -9,7 +9,7 @@ module LinkedRails
9
9
 
10
10
  def data
11
11
  data = []
12
- iri.each_statement do |statement|
12
+ iri.try(:each_statement) do |statement|
13
13
  next unless include_data_statement?(statement)
14
14
 
15
15
  statement.graph_name = ::RDF::Serializers.config.default_graph
@@ -41,6 +41,8 @@ module LinkedRails
41
41
  end
42
42
 
43
43
  def include_data_statement?(statement)
44
+ return false if statement.subject.node? || statement.object.node?
45
+
44
46
  statement.predicate != Vocab.rdfs.label &&
45
47
  statement.predicate != Vocab.rdfs.range &&
46
48
  statement.predicate != Vocab.rdfs.domain