linked_rails 0.0.3 → 0.0.4.pre.g0ec6e30ca

Sign up to get free protection for your applications and to get access to all the features.
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