linked_rails 0.0.3 → 0.0.4
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 +4 -4
- data/app/controllers/linked_rails/bulk_controller.rb +26 -6
- data/app/controllers/linked_rails/enum_values_controller.rb +0 -42
- data/app/models/linked_rails/actions/item.rb +46 -30
- data/app/models/linked_rails/actions/list.rb +6 -25
- data/app/models/linked_rails/collection/configuration.rb +55 -0
- data/app/models/linked_rails/collection/filter.rb +1 -1
- data/app/models/linked_rails/collection/filter_field.rb +5 -1
- data/app/models/linked_rails/collection/filter_option.rb +1 -1
- data/app/models/linked_rails/collection/filterable.rb +3 -8
- data/app/models/linked_rails/collection/infinite.rb +102 -0
- data/app/models/linked_rails/collection/infinite_view.rb +1 -90
- data/app/models/linked_rails/collection/iri.rb +33 -52
- data/app/models/linked_rails/collection/iri_mapping.rb +14 -6
- data/app/models/linked_rails/collection/paginated.rb +45 -0
- data/app/models/linked_rails/collection/paginated_view.rb +1 -33
- data/app/models/linked_rails/collection/sortable.rb +1 -9
- data/app/models/linked_rails/collection/sorting.rb +1 -1
- data/app/models/linked_rails/collection/view.rb +15 -5
- data/app/models/linked_rails/collection.rb +33 -67
- data/app/models/linked_rails/creative_work.rb +1 -1
- data/app/models/linked_rails/entry_point.rb +8 -5
- data/app/models/linked_rails/enum_value.rb +39 -1
- data/app/models/linked_rails/form/field/resource_field.rb +2 -0
- data/app/models/linked_rails/form/field.rb +6 -8
- data/app/models/linked_rails/form/field_factory.rb +21 -10
- data/app/models/linked_rails/form/group.rb +2 -2
- data/app/models/linked_rails/form/page.rb +4 -4
- data/app/models/linked_rails/form.rb +13 -15
- data/app/models/linked_rails/manifest.rb +8 -2
- data/app/models/linked_rails/menus/item.rb +1 -1
- data/app/models/linked_rails/menus/list.rb +3 -2
- data/app/models/linked_rails/ontology/base.rb +1 -1
- data/app/models/linked_rails/ontology/class.rb +3 -3
- data/app/models/linked_rails/ontology.rb +5 -5
- data/app/models/linked_rails/sequence.rb +2 -2
- data/app/models/linked_rails/shacl/property_shape.rb +1 -1
- data/app/models/linked_rails/widget.rb +1 -1
- data/app/serializers/linked_rails/actions/item_serializer.rb +2 -1
- data/app/serializers/linked_rails/collection_serializer.rb +7 -2
- data/app/serializers/linked_rails/entry_point_serializer.rb +1 -1
- data/app/serializers/linked_rails/ontology_serializer.rb +2 -2
- data/lib/generators/linked_rails/install/install_generator.rb +5 -8
- data/lib/generators/linked_rails/install/templates/README +2 -0
- data/lib/generators/linked_rails/install/templates/app_menu_list.rb +36 -7
- data/lib/generators/linked_rails/install/templates/application_menu_list.rb +40 -1
- data/lib/generators/linked_rails/install/templates/initializer.rb +1 -2
- data/lib/generators/linked_rails/install/templates/locales.yml +12 -0
- data/lib/generators/linked_rails/install/templates/vocab.rb +1 -0
- data/lib/generators/linked_rails/install/templates/vocab.yml +2 -2
- data/lib/generators/linked_rails/model/model_generator.rb +0 -1
- data/lib/generators/linked_rails/model/templates/controller.rb.tt +5 -1
- data/lib/generators/linked_rails/model/templates/form.rb.tt +3 -0
- data/lib/generators/linked_rails/model/templates/menu_list.rb.tt +15 -0
- data/lib/generators/linked_rails/model/templates/policy.rb.tt +13 -0
- data/lib/generators/linked_rails/model/templates/serializer.rb.tt +1 -1
- data/lib/linked_rails/active_response/controller/collections.rb +1 -1
- data/lib/linked_rails/active_response/controller/crud_defaults.rb +3 -3
- data/lib/linked_rails/active_response/controller/params.rb +5 -5
- data/lib/linked_rails/active_response/controller.rb +11 -0
- data/lib/linked_rails/active_response/responders/rdf.rb +19 -10
- data/lib/linked_rails/callable_variable.rb +1 -1
- data/lib/linked_rails/collection_params_parser.rb +93 -0
- data/lib/linked_rails/controller/actionable.rb +118 -0
- data/lib/linked_rails/controller/authorization.rb +6 -0
- data/lib/linked_rails/controller/default_actions/create.rb +52 -0
- data/lib/linked_rails/controller/default_actions/destroy.rb +42 -0
- data/lib/linked_rails/controller/default_actions/update.rb +43 -0
- data/lib/linked_rails/controller.rb +20 -3
- data/lib/linked_rails/enhancements/creatable/controller.rb +1 -1
- data/lib/linked_rails/enhancements/destroyable/controller.rb +1 -1
- data/lib/linked_rails/enhancements/updatable/controller.rb +1 -1
- data/lib/linked_rails/enhancements.rb +0 -16
- data/lib/linked_rails/helpers/delta_helper.rb +26 -3
- data/lib/linked_rails/helpers/ontola_actions_helper.rb +2 -2
- data/lib/linked_rails/helpers/resource_helper.rb +1 -1
- data/lib/linked_rails/iri_mapper.rb +17 -39
- data/lib/linked_rails/middleware/linked_data_params.rb +7 -127
- data/lib/linked_rails/model/actionable.rb +69 -0
- data/lib/linked_rails/model/collections.rb +195 -39
- data/lib/linked_rails/model/dirty.rb +1 -1
- data/lib/linked_rails/model/enhancements.rb +1 -6
- data/lib/linked_rails/model/filtering.rb +2 -4
- data/lib/linked_rails/model/indexable.rb +5 -15
- data/lib/linked_rails/model/iri.rb +15 -17
- data/lib/linked_rails/model/iri_mapping.rb +35 -6
- data/lib/linked_rails/model/menuable.rb +34 -0
- data/lib/linked_rails/model/serialization.rb +0 -9
- data/lib/linked_rails/model/singularable.rb +55 -0
- data/lib/linked_rails/model/sorting.rb +0 -5
- data/lib/linked_rails/model/tables.rb +26 -0
- data/lib/linked_rails/model.rb +13 -5
- data/lib/linked_rails/params_parser.rb +131 -55
- data/lib/linked_rails/policy/attribute_conditions.rb +2 -2
- data/lib/linked_rails/policy.rb +24 -17
- data/lib/linked_rails/rdf_error.rb +2 -2
- data/lib/linked_rails/routes.rb +37 -22
- data/lib/linked_rails/serializer/actionable.rb +27 -0
- data/lib/linked_rails/serializer/menuable.rb +31 -0
- data/lib/linked_rails/serializer/singularable.rb +26 -0
- data/lib/linked_rails/serializer.rb +23 -10
- data/lib/linked_rails/test_methods.rb +114 -0
- data/lib/linked_rails/translate.rb +19 -9
- data/lib/linked_rails/uri_template.rb +30 -0
- data/lib/linked_rails/version.rb +1 -1
- data/lib/linked_rails/vocab.rb +8 -0
- data/lib/linked_rails.rb +25 -12
- data/lib/rails/welcome_controller.rb +3 -3
- data/lib/rdf/query_fix.rb +15 -0
- metadata +22 -25
- data/app/models/linked_rails/actions/default_actions/create.rb +0 -60
- data/app/models/linked_rails/actions/default_actions/destroy.rb +0 -45
- data/app/models/linked_rails/actions/default_actions/update.rb +0 -50
- data/app/models/linked_rails/actions/default_actions.rb +0 -17
- data/lib/generators/linked_rails/install/templates/application_action_list.rb +0 -3
- data/lib/generators/linked_rails/model/templates/action_list.rb.tt +0 -6
- data/lib/linked_rails/enhancements/actionable/model.rb +0 -71
- data/lib/linked_rails/enhancements/actionable/serializer.rb +0 -25
- data/lib/linked_rails/enhancements/creatable/action.rb +0 -15
- data/lib/linked_rails/enhancements/destroyable/action.rb +0 -15
- data/lib/linked_rails/enhancements/destroyable/routing.rb +0 -19
- data/lib/linked_rails/enhancements/indexable/model.rb +0 -10
- data/lib/linked_rails/enhancements/menuable/model.rb +0 -36
- data/lib/linked_rails/enhancements/menuable/serializer.rb +0 -33
- data/lib/linked_rails/enhancements/route_concerns.rb +0 -56
- data/lib/linked_rails/enhancements/singularable/controller.rb +0 -43
- data/lib/linked_rails/enhancements/singularable/model.rb +0 -47
- data/lib/linked_rails/enhancements/singularable/serializer.rb +0 -28
- data/lib/linked_rails/enhancements/tableable/model.rb +0 -28
- data/lib/linked_rails/enhancements/updatable/action.rb +0 -15
- data/lib/linked_rails/enhancements/updatable/routing.rb +0 -20
|
@@ -41,7 +41,10 @@ module LinkedRails
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def changes_triples # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
44
|
-
|
|
44
|
+
serializer_class = RDF::Serializers.serializer_for(current_resource)
|
|
45
|
+
return [] if serializer_class.blank?
|
|
46
|
+
|
|
47
|
+
serializer = serializer_class.new(current_resource)
|
|
45
48
|
|
|
46
49
|
current_resource.previous_changes_by_predicate.map do |predicate, (_old_value, _new_value)|
|
|
47
50
|
serializer_attributes = current_resource.class.predicate_mapping[predicate]
|
|
@@ -75,11 +78,31 @@ module LinkedRails
|
|
|
75
78
|
end
|
|
76
79
|
|
|
77
80
|
def resource_added_delta(resource)
|
|
78
|
-
invalidate_parent_collections_delta(resource)
|
|
81
|
+
invalidate_parent_collections_delta(resource) + singular_added_delta(resource)
|
|
79
82
|
end
|
|
80
83
|
|
|
81
84
|
def resource_removed_delta(resource)
|
|
82
|
-
invalidate_parent_collections_delta(resource)
|
|
85
|
+
invalidate_parent_collections_delta(resource) + singular_removed_delta(resource)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def same_as_statement(from, to)
|
|
89
|
+
[
|
|
90
|
+
from,
|
|
91
|
+
Vocab.owl.sameAs,
|
|
92
|
+
to
|
|
93
|
+
]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def singular_added_delta(resource)
|
|
97
|
+
return [] unless resource.try(:singular_resource?)
|
|
98
|
+
|
|
99
|
+
[same_as_statement(resource.singular_iri, resource.iri)]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def singular_removed_delta(resource)
|
|
103
|
+
return [] unless resource.try(:singular_resource?)
|
|
104
|
+
|
|
105
|
+
[invalidate_resource_delta(resource.singular_iri)]
|
|
83
106
|
end
|
|
84
107
|
end
|
|
85
108
|
end
|
|
@@ -12,8 +12,8 @@ module LinkedRails
|
|
|
12
12
|
Vocab.libro["actions/copyToClipboard?#{{value: value}.to_param}"]
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def ontola_dialog_action(resource, opener: nil)
|
|
16
|
-
Vocab.libro["actions/dialog/alert?#{{resource: resource, opener: opener}.compact.to_param}"]
|
|
15
|
+
def ontola_dialog_action(resource, opener: nil, size: nil)
|
|
16
|
+
Vocab.libro["actions/dialog/alert?#{{resource: resource, opener: opener, size: size}.compact.to_param}"]
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def ontola_dialog_close_action
|
|
@@ -12,6 +12,7 @@ module LinkedRails
|
|
|
12
12
|
# => {
|
|
13
13
|
# action: 'show',
|
|
14
14
|
# class: Resource,
|
|
15
|
+
# iri: 'https://example.com/resource/1?foo=bar',
|
|
15
16
|
# params: {
|
|
16
17
|
# id: '1',
|
|
17
18
|
# foo: 'bar'
|
|
@@ -22,6 +23,7 @@ module LinkedRails
|
|
|
22
23
|
# => {
|
|
23
24
|
# action: 'index',
|
|
24
25
|
# class: Resource,
|
|
26
|
+
# iri: 'https://example.com/resources?foo=bar',
|
|
25
27
|
# params: {
|
|
26
28
|
# foo: 'bar'
|
|
27
29
|
# }
|
|
@@ -35,38 +37,21 @@ module LinkedRails
|
|
|
35
37
|
query = Rack::Utils.parse_nested_query(URI(iri.to_s).query)
|
|
36
38
|
params = Rails.application.routes.recognize_path(iri.to_s, method: method)
|
|
37
39
|
|
|
38
|
-
route_params_to_opts(params.merge(query))
|
|
40
|
+
route_params_to_opts(params.merge(query), iri.to_s)
|
|
39
41
|
rescue ActionController::RoutingError
|
|
40
42
|
EMPTY_IRI_OPTS.dup
|
|
41
43
|
end
|
|
42
44
|
|
|
43
|
-
def index_resource_from_iri(iri, user_context)
|
|
44
|
-
ensure_absolute_iri!(iri)
|
|
45
|
-
|
|
46
|
-
opts = opts_from_iri(iri)
|
|
47
|
-
index_resource_from_opts(opts, user_context)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def index_resource_from_iri!(iri, user_context)
|
|
51
|
-
index_resource_from_iri(iri, user_context) || raise(ActiveRecord::RecordNotFound)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def index_resource_from_opts(opts, user_context)
|
|
55
|
-
opts[:class]&.requested_index_resource(opts[:params], user_context) if opts[:action] == 'index'
|
|
56
|
-
end
|
|
57
|
-
|
|
58
45
|
def parent_from_params(params, user_context)
|
|
59
46
|
return unless params.key?(:parent_iri)
|
|
60
47
|
|
|
61
|
-
parent_iri = "/#{params[:parent_iri]}"
|
|
62
|
-
opts = LinkedRails.iri_mapper.opts_from_iri(parent_iri)
|
|
63
|
-
opts[:params] = params.except(:parent_iri, :singular_route).merge(opts[:params])
|
|
48
|
+
parent_iri = LinkedRails.iri(path: "/#{params[:parent_iri]}")
|
|
64
49
|
|
|
65
|
-
LinkedRails.iri_mapper.
|
|
50
|
+
LinkedRails.iri_mapper.resource_from_iri(parent_iri, user_context)
|
|
66
51
|
end
|
|
67
52
|
|
|
68
53
|
def resource_from_iri(iri, user_context)
|
|
69
|
-
|
|
54
|
+
return nil unless absolute_iri?(iri)
|
|
70
55
|
|
|
71
56
|
opts = opts_from_iri(iri)
|
|
72
57
|
resource_from_opts(opts, user_context)
|
|
@@ -80,7 +65,7 @@ module LinkedRails
|
|
|
80
65
|
opts[:class]&.requested_resource(opts, user_context)
|
|
81
66
|
end
|
|
82
67
|
|
|
83
|
-
def route_params_to_opts(params)
|
|
68
|
+
def route_params_to_opts(params, iri)
|
|
84
69
|
controller_class = class_for_controller(params[:controller])
|
|
85
70
|
|
|
86
71
|
return EMPTY_IRI_OPTS.dup if controller_class.blank?
|
|
@@ -88,27 +73,17 @@ module LinkedRails
|
|
|
88
73
|
{
|
|
89
74
|
action: params[:action],
|
|
90
75
|
class: controller_class,
|
|
91
|
-
|
|
76
|
+
iri: iri,
|
|
77
|
+
params: sanitized_route_params(controller_class, params)
|
|
92
78
|
}.with_indifferent_access
|
|
93
79
|
end
|
|
94
80
|
|
|
95
|
-
|
|
96
|
-
ensure_absolute_iri!(iri)
|
|
97
|
-
|
|
98
|
-
opts = opts_from_iri(iri)
|
|
99
|
-
single_resource_from_opts(opts, user_context)
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def single_resource_from_iri!(iri, user_context)
|
|
103
|
-
single_resource_from_iri(iri, user_context) || raise(ActiveRecord::RecordNotFound)
|
|
104
|
-
end
|
|
81
|
+
private
|
|
105
82
|
|
|
106
|
-
def
|
|
107
|
-
|
|
83
|
+
def absolute_iri?(iri)
|
|
84
|
+
iri.present? && !URI(iri).relative?
|
|
108
85
|
end
|
|
109
86
|
|
|
110
|
-
private
|
|
111
|
-
|
|
112
87
|
def class_for_controller(controller)
|
|
113
88
|
return if controller.blank?
|
|
114
89
|
|
|
@@ -117,8 +92,11 @@ module LinkedRails
|
|
|
117
92
|
.try(:controller_class)
|
|
118
93
|
end
|
|
119
94
|
|
|
120
|
-
def
|
|
121
|
-
|
|
95
|
+
def sanitized_route_params(controller_class, params)
|
|
96
|
+
new_params = params.except(:action, :controller)
|
|
97
|
+
nested_key = :"#{controller_class.name.tableize.singularize}_id"
|
|
98
|
+
new_params[:id] ||= new_params.delete(nested_key) if new_params.key?(nested_key)
|
|
99
|
+
new_params
|
|
122
100
|
end
|
|
123
101
|
end
|
|
124
102
|
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module LinkedRails
|
|
4
4
|
module Middleware
|
|
5
|
-
class LinkedDataParams
|
|
5
|
+
class LinkedDataParams
|
|
6
6
|
def initialize(app)
|
|
7
7
|
@app = app
|
|
8
8
|
end
|
|
@@ -17,33 +17,8 @@ module LinkedRails
|
|
|
17
17
|
|
|
18
18
|
private
|
|
19
19
|
|
|
20
|
-
def
|
|
21
|
-
|
|
22
|
-
when nil
|
|
23
|
-
hash[key] = value
|
|
24
|
-
when Hash
|
|
25
|
-
hash[key].merge!(value)
|
|
26
|
-
when Array
|
|
27
|
-
hash[key].append(value)
|
|
28
|
-
else
|
|
29
|
-
hash[key] = [hash[key], value]
|
|
30
|
-
end
|
|
31
|
-
hash
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def associated_class_from_params(reflection, graph, subject)
|
|
35
|
-
return reflection.klass unless reflection.polymorphic?
|
|
36
|
-
|
|
37
|
-
query = graph.query(subject: subject, predicate: Vocab.rdfv[:type])
|
|
38
|
-
if query.empty?
|
|
39
|
-
raise("No type given for '#{subject}' referenced by polymorphic association '#{reflection.name}'")
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
iri_to_class(query.first.object)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def blob_attribute(base_params, value)
|
|
46
|
-
base_params["<#{value}>"] if value.starts_with?(Vocab.ll['blobs/'])
|
|
20
|
+
def add_param_from_query(data, target_class, key, value)
|
|
21
|
+
data[target_class.predicate_mapping[RDF::URI(key)].key] = value
|
|
47
22
|
end
|
|
48
23
|
|
|
49
24
|
def convert_query_params(request, target_class)
|
|
@@ -56,17 +31,6 @@ module LinkedRails
|
|
|
56
31
|
request.update_param(class_key, data) if data.present?
|
|
57
32
|
end
|
|
58
33
|
|
|
59
|
-
def add_param_from_query(data, target_class, key, value)
|
|
60
|
-
data[target_class.predicate_mapping[RDF::URI(key)].key] = value
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def enum_attribute(klass, key, value)
|
|
64
|
-
opts = RDF::Serializers.serializer_for(klass).try(:enum_options, key)
|
|
65
|
-
return if opts.blank?
|
|
66
|
-
|
|
67
|
-
opts.detect { |_k, options| options.iri == value }&.first&.to_s
|
|
68
|
-
end
|
|
69
|
-
|
|
70
34
|
def graph_from_request(request)
|
|
71
35
|
request_graph = request.delete_param("<#{Vocab.ll[:graph].value}>")
|
|
72
36
|
return if request_graph.blank?
|
|
@@ -79,33 +43,6 @@ module LinkedRails
|
|
|
79
43
|
)
|
|
80
44
|
end
|
|
81
45
|
|
|
82
|
-
def iri_to_class(iri)
|
|
83
|
-
iri.to_s.split(LinkedRails.app_vocab.to_s).pop&.classify&.safe_constantize ||
|
|
84
|
-
ApplicationRecord.descendants.detect { |klass| klass.iri == iri }
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def logger
|
|
88
|
-
Rails.logger
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def nested_attributes(base_params, graph, subject, klass, association, collection) # rubocop:disable Metrics/ParameterLists
|
|
92
|
-
nested_resources =
|
|
93
|
-
if graph.query([subject, Vocab.rdfv[:first], nil]).present?
|
|
94
|
-
nested_attributes_from_list(base_params, graph, subject, klass)
|
|
95
|
-
else
|
|
96
|
-
parsed = parse_nested_resource(base_params, graph, subject, klass)
|
|
97
|
-
collection ? {rand(1_000_000_000).to_s => parsed} : parsed
|
|
98
|
-
end
|
|
99
|
-
["#{association}_attributes", nested_resources]
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def nested_attributes_from_list(base_params, graph, subject, klass)
|
|
103
|
-
Hash[
|
|
104
|
-
RDF::List.new(subject: subject, graph: graph)
|
|
105
|
-
.map { |nested| [rand(1_000_000_000).to_s, parse_nested_resource(base_params, graph, nested, klass)] }
|
|
106
|
-
]
|
|
107
|
-
end
|
|
108
|
-
|
|
109
46
|
# Converts a serialized graph from a multipart request body to a nested
|
|
110
47
|
# attributes hash.
|
|
111
48
|
#
|
|
@@ -135,72 +72,13 @@ module LinkedRails
|
|
|
135
72
|
convert_query_params(request, target_class)
|
|
136
73
|
end
|
|
137
74
|
|
|
138
|
-
def parse_nested_resource(base_params, graph, subject, klass)
|
|
139
|
-
resource = parse_resource(base_params, graph, subject, klass)
|
|
140
|
-
resource[:id] ||= LinkedRails.iri_mapper.opts_from_iri(subject)[:params][:id] if subject.iri?
|
|
141
|
-
resource
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
# Recursively parses a resource from graph
|
|
145
|
-
def parse_resource(base_params, graph, subject, klass)
|
|
146
|
-
graph
|
|
147
|
-
.query([subject])
|
|
148
|
-
.map { |statement| parse_statement(base_params, graph, statement, klass) }
|
|
149
|
-
.compact
|
|
150
|
-
.reduce({}) { |h, (k, v)| add_param(h, k, v) }
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def parse_statement(base_params, graph, statement, klass)
|
|
154
|
-
field = serializer_field(klass, statement.predicate)
|
|
155
|
-
if field.is_a?(FastJsonapi::Attribute)
|
|
156
|
-
parsed_attribute(base_params, klass, field.key, statement.object.value)
|
|
157
|
-
elsif field.is_a?(FastJsonapi::Relationship)
|
|
158
|
-
parsed_association(base_params, graph, statement.object, klass, field.association || field.key)
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
def parsed_association(base_params, graph, object, klass, association)
|
|
163
|
-
reflection = klass.reflect_on_association(association) || raise("#{association} not found for #{klass}")
|
|
164
|
-
|
|
165
|
-
if graph.has_subject?(object)
|
|
166
|
-
association_klass = associated_class_from_params(reflection, graph, object)
|
|
167
|
-
nested_attributes(base_params, graph, object, association_klass, association, reflection.collection?)
|
|
168
|
-
elsif object.iri?
|
|
169
|
-
attributes_from_iri(object, association, reflection)
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def attributes_from_iri(object, association, reflection)
|
|
174
|
-
if reflection.options[:through]
|
|
175
|
-
key = reflection.has_one? ? "#{association}_id" : "#{association.to_s.singularize}_ids"
|
|
176
|
-
elsif reflection.belongs_to?
|
|
177
|
-
key = reflection.foreign_key
|
|
178
|
-
end
|
|
179
|
-
return unless key
|
|
180
|
-
|
|
181
|
-
resource = LinkedRails.iri_mapper.resource_from_iri(object, nil)
|
|
182
|
-
value = resource&.send(reflection.association_primary_key)
|
|
183
|
-
|
|
184
|
-
[key, value] if value
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
def parsed_attribute(base_params, klass, key, value)
|
|
188
|
-
[key, blob_attribute(base_params, value) || enum_attribute(klass, key, value) || value]
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def serializer_field(klass, predicate)
|
|
192
|
-
field = klass.try(:predicate_mapping).try(:[], predicate)
|
|
193
|
-
logger.info("#{predicate} not found for #{klass || 'nil'}") if field.blank?
|
|
194
|
-
field
|
|
195
|
-
end
|
|
196
|
-
|
|
197
75
|
def target_class_from_path(request) # rubocop:disable Metrics/AbcSize
|
|
198
76
|
opts = LinkedRails.iri_mapper.opts_from_iri(
|
|
199
77
|
request.base_url + request.env['REQUEST_URI'],
|
|
200
78
|
method: request.request_method
|
|
201
79
|
)
|
|
202
80
|
|
|
203
|
-
logger.info("No class found for #{request.base_url + request.env['REQUEST_URI']}") unless opts[:class]
|
|
81
|
+
Rails.logger.info("No class found for #{request.base_url + request.env['REQUEST_URI']}") unless opts[:class]
|
|
204
82
|
|
|
205
83
|
opts[:class]
|
|
206
84
|
end
|
|
@@ -215,7 +93,9 @@ module LinkedRails
|
|
|
215
93
|
|
|
216
94
|
def update_target_params(request, graph, target_class)
|
|
217
95
|
key = target_class.to_s.demodulize.underscore
|
|
218
|
-
|
|
96
|
+
|
|
97
|
+
parser = ParamsParser.new(graph: graph, params: request.params)
|
|
98
|
+
from_body = parser.parse_resource(Vocab.ll[:targetResource], target_class)
|
|
219
99
|
|
|
220
100
|
request.update_param(key, from_body.merge(request.params[key] || {}))
|
|
221
101
|
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LinkedRails
|
|
4
|
+
module Model
|
|
5
|
+
module Actionable
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
def actions(user_context = nil)
|
|
9
|
+
action_list(user_context).actions
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def action(tag, user_context = nil)
|
|
13
|
+
actions(user_context).find { |a| a.tag == tag }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def action_list(user_context)
|
|
17
|
+
@action_list ||= {}
|
|
18
|
+
@action_list[user_context] ||= self.class.action_list.new(resource: self, user_context: user_context)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def action_triples
|
|
22
|
+
@action_triples ||= triples_for_actions(actions) + triples_for_actions(collection_actions)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def collection_actions
|
|
26
|
+
(try(:collections) || []).map do |opts|
|
|
27
|
+
collection_for(opts[:name]).actions
|
|
28
|
+
end.flatten
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def favorite_actions
|
|
32
|
+
actions.filter(&:favorite)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def triples_for_actions(actions)
|
|
38
|
+
actions.flat_map do |action|
|
|
39
|
+
[
|
|
40
|
+
[iri, action.predicate, action.iri],
|
|
41
|
+
[iri, Vocab.schema.potentialAction, action.iri]
|
|
42
|
+
]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
module ClassMethods
|
|
47
|
+
def action_list
|
|
48
|
+
return @action_list if @action_list.try(:actionable_class) == self
|
|
49
|
+
|
|
50
|
+
@action_list = defined_action_list || define_action_list
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def action_superclass
|
|
56
|
+
superclass.try(:action_list) || LinkedRails.action_list_parent_class
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def defined_action_list
|
|
60
|
+
'ActionList'.safe_constantize
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def define_action_list
|
|
64
|
+
const_set('ActionList', Class.new(action_superclass))
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -5,8 +5,175 @@ module LinkedRails
|
|
|
5
5
|
module Collections
|
|
6
6
|
extend ActiveSupport::Concern
|
|
7
7
|
|
|
8
|
+
COLLECTION_CUSTOMIZABLE_OPTIONS = {
|
|
9
|
+
# display [Sym] The default display type.
|
|
10
|
+
# Choose between :grid, :settingsTable, :table, :card, :default
|
|
11
|
+
display: :default,
|
|
12
|
+
# grid_max_columns [Integer] The default amount of columns to use in a grid.
|
|
13
|
+
grid_max_columns: 3,
|
|
14
|
+
# page_size [Integer] The default page size.
|
|
15
|
+
page_size: 20,
|
|
16
|
+
# table_type [Sym] The columns to use in the table.
|
|
17
|
+
table_type: lambda {
|
|
18
|
+
case display&.to_sym
|
|
19
|
+
when :table
|
|
20
|
+
:default
|
|
21
|
+
when :settingsTable
|
|
22
|
+
:settings
|
|
23
|
+
end
|
|
24
|
+
},
|
|
25
|
+
# title String The default title.
|
|
26
|
+
title: -> { title_from_translation },
|
|
27
|
+
# type [Sym] The default pagination type.
|
|
28
|
+
# Choose between :paginated, :infinite.
|
|
29
|
+
type: :paginated
|
|
30
|
+
}.freeze
|
|
31
|
+
COLLECTION_STATIC_OPTIONS = {
|
|
32
|
+
# association [Sym] The association of the collection items.
|
|
33
|
+
association: nil,
|
|
34
|
+
# association_base [Scope, Array] The items of the collection.
|
|
35
|
+
association_base: -> { apply_scope(sorted_association(filtered_association)) },
|
|
36
|
+
# association_class [Class] The class of the collection items.
|
|
37
|
+
association_class: nil,
|
|
38
|
+
# association_scope [Sym] The scope applied to the collection.
|
|
39
|
+
association_scope: nil,
|
|
40
|
+
# collection_class [Class] The base class of the collection.
|
|
41
|
+
# If you want to use a class other than LinkedRails.collection_class.
|
|
42
|
+
collection_class: nil,
|
|
43
|
+
# collection_class [Hash] The default filters applied to the collection.
|
|
44
|
+
default_filters: {},
|
|
45
|
+
# collection_class [Array<Hash>] The default sortings applied to the collection.
|
|
46
|
+
default_sortings: [{key: Vocab.schema.dateCreated, direction: :desc}],
|
|
47
|
+
# iri_template_keys [Array<Sym>] Custom query keys for the iri template
|
|
48
|
+
iri_template_keys: [],
|
|
49
|
+
# joins [Array<Sym>, Sym] The associations to join
|
|
50
|
+
joins: nil,
|
|
51
|
+
# parent [Instance] The default parent of a collection.
|
|
52
|
+
parent: nil,
|
|
53
|
+
# parent_iri [Array<String>] The iri elements of the parent
|
|
54
|
+
parent_iri: -> { parent&.iri_elements },
|
|
55
|
+
# part_of [Instance] The record to serialize as isPartOf
|
|
56
|
+
part_of: -> { parent },
|
|
57
|
+
# policy_scope [Scope] The policy scope class to be used for scoping
|
|
58
|
+
# Set to false to skip scoping
|
|
59
|
+
policy_scope: -> { policy ? policy::Scope : Pundit::PolicyFinder.new(filtered_association).scope! },
|
|
60
|
+
# route_key [Symbol, String] The route key for the association
|
|
61
|
+
route_key: nil
|
|
62
|
+
}.freeze
|
|
63
|
+
COLLECTION_OPTIONS = COLLECTION_CUSTOMIZABLE_OPTIONS.merge(COLLECTION_STATIC_OPTIONS)
|
|
64
|
+
|
|
65
|
+
module ClassMethods
|
|
66
|
+
def collection_iri(**opts)
|
|
67
|
+
LinkedRails.iri(path: collection_root_relative_iri(**opts))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Sets the defaults for all collections for this class.
|
|
71
|
+
# Can be overridden by #with_collection, called from associated models,
|
|
72
|
+
# or by passing parameters in an iri.
|
|
73
|
+
# @param [Hash] options
|
|
74
|
+
def collection_options(**options)
|
|
75
|
+
initialize_default_collection_opts
|
|
76
|
+
|
|
77
|
+
options.each do |key, value|
|
|
78
|
+
raise("Invalid key passed to collection_options: #{key}") unless valid_collection_option?(key)
|
|
79
|
+
|
|
80
|
+
_default_collection_opts[key] = value
|
|
81
|
+
end
|
|
82
|
+
_default_collection_opts[:iri_template] = LinkedRails.collection_class.generate_iri_template(
|
|
83
|
+
_default_collection_opts[:iri_template_keys]
|
|
84
|
+
)
|
|
85
|
+
_default_collection_opts
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def collection_root_relative_iri(**opts)
|
|
89
|
+
opts[:filter] = LinkedRails.collection_class.filter_iri_opts(opts[:filter]) if opts.key?(:filter)
|
|
90
|
+
opts[:route_key] = collection_route_key
|
|
91
|
+
default_collection_option(:iri_template).expand(**opts)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def collection_route_key
|
|
95
|
+
default_collection_option(:route_key) || route_key
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def default_collection_options
|
|
99
|
+
initialize_default_collection_opts
|
|
100
|
+
|
|
101
|
+
_default_collection_opts
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def default_collection_option(key)
|
|
105
|
+
default_collection_options[key]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Defines a collection to be used in {collection_for}
|
|
109
|
+
# @see Ldable#collection_for
|
|
110
|
+
# @note Adds a instance_method <name>_collection
|
|
111
|
+
# @param [Hash] name as to be used in {collection_for}
|
|
112
|
+
# @param [Hash] options See COLLECTION_OPTIONS
|
|
113
|
+
# @return [Collection]
|
|
114
|
+
def with_collection(name, **options) # rubocop:disable Metrics/AbcSize
|
|
115
|
+
options[:association] ||= name.to_sym
|
|
116
|
+
options[:association_class] ||= name.to_s.classify.constantize
|
|
117
|
+
merged_options = options[:association_class].default_collection_options.merge(options)
|
|
118
|
+
merged_options[:iri_template] = LinkedRails.collection_class.generate_iri_template(
|
|
119
|
+
merged_options[:iri_template_keys]
|
|
120
|
+
)
|
|
121
|
+
collections_add(name: name, options: merged_options)
|
|
122
|
+
|
|
123
|
+
define_method "#{name.to_s.singularize}_collection" do |opts = {}|
|
|
124
|
+
collection_for(name, **opts)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
def collections_add(opts)
|
|
131
|
+
initialize_collections
|
|
132
|
+
collections.delete_if { |c| c[:name] == opts[:name] }
|
|
133
|
+
opts[:options] = sanitized_collection_options(opts[:options])
|
|
134
|
+
collections.append(opts)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def initialize_collections
|
|
138
|
+
return if collections && method(:collections).owner == singleton_class
|
|
139
|
+
|
|
140
|
+
self.collections = superclass.try(:collections)&.dup || []
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def initialize_default_collection_opts # rubocop:disable Metrics/AbcSize
|
|
144
|
+
return if _default_collection_opts && method(:_default_collection_opts).owner == singleton_class
|
|
145
|
+
|
|
146
|
+
self._default_collection_opts = (superclass.try(:_default_collection_opts) || COLLECTION_OPTIONS).dup
|
|
147
|
+
|
|
148
|
+
_default_collection_opts[:collection_class] ||= LinkedRails.collection_class
|
|
149
|
+
_default_collection_opts[:association_class] = self
|
|
150
|
+
_default_collection_opts[:iri_template] = LinkedRails.collection_class.generate_iri_template(
|
|
151
|
+
_default_collection_opts[:iri_template_keys]
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
_default_collection_opts
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def sanitized_collection_options(opts)
|
|
158
|
+
opts.each_with_object(HashWithIndifferentAccess.new) do |(key, value), hash|
|
|
159
|
+
raise("Invalid key passed to with_collection: #{key}") unless valid_collection_option?(key.to_sym)
|
|
160
|
+
|
|
161
|
+
hash_key = COLLECTION_CUSTOMIZABLE_OPTIONS.key?(key.to_sym) ? "default_#{key}" : key
|
|
162
|
+
|
|
163
|
+
hash[hash_key] = value
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def valid_collection_option?(key)
|
|
168
|
+
COLLECTION_OPTIONS.key?(key) || key == :iri_template
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
8
172
|
included do
|
|
9
173
|
class_attribute :collections
|
|
174
|
+
class_attribute :_default_collection_opts,
|
|
175
|
+
instance_accessor: false,
|
|
176
|
+
instance_predicate: false
|
|
10
177
|
end
|
|
11
178
|
|
|
12
179
|
# Initialises a {Collection} for one of the collections defined by {has_collection}
|
|
@@ -18,19 +185,42 @@ module LinkedRails
|
|
|
18
185
|
# @param [ApplicationRecord] part_of
|
|
19
186
|
# @param [Hash] opts Additional options to be passed to the collection.
|
|
20
187
|
# @return [Collection]
|
|
21
|
-
def collection_for(name, instance_opts
|
|
22
|
-
collection_opts =
|
|
188
|
+
def collection_for(name, **instance_opts)
|
|
189
|
+
collection_opts = collection_options_for(name).dup
|
|
23
190
|
return if collection_opts.blank?
|
|
24
191
|
|
|
25
192
|
collection_opts[:name] = name
|
|
26
193
|
collection_opts[:parent] = self
|
|
27
|
-
|
|
28
|
-
|
|
194
|
+
collection_class =
|
|
195
|
+
collection_opts.delete(:collection_class) ||
|
|
196
|
+
collection_opts[:association_class].default_collection_option(:collection_class) ||
|
|
197
|
+
LinkedRails.collection_class
|
|
29
198
|
collection_class.collection_or_view(collection_opts, instance_opts)
|
|
30
199
|
end
|
|
31
200
|
|
|
201
|
+
def collection_iri(collection, **opts)
|
|
202
|
+
LinkedRails.iri(path: collection_root_relative_iri(collection, **opts))
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def collection_options_for(name)
|
|
206
|
+
opts = collections.detect { |c| c[:name] == name.to_sym }
|
|
207
|
+
raise("Collection #{name} not found for #{self}") unless opts
|
|
208
|
+
|
|
209
|
+
opts[:options] || {}
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def collection_root_relative_iri(collection, **opts)
|
|
213
|
+
collection_opts = collection_options_for(collection).dup
|
|
214
|
+
template = collection_opts[:iri_template]
|
|
215
|
+
klass = collection_opts[:association_class]
|
|
216
|
+
opts[:route_key] = collection_opts[:route_key] || klass.collection_route_key
|
|
217
|
+
opts[:parent_iri] = iri_elements
|
|
218
|
+
|
|
219
|
+
template.expand(**opts).to_s
|
|
220
|
+
end
|
|
221
|
+
|
|
32
222
|
def parent_collections(user_context)
|
|
33
|
-
return [] if try(:parent).try(:collections).blank?
|
|
223
|
+
return [self.class.root_collection(user_context: user_context)] if try(:parent).try(:collections).blank?
|
|
34
224
|
|
|
35
225
|
parent_collections_for(parent, user_context)
|
|
36
226
|
end
|
|
@@ -43,40 +233,6 @@ module LinkedRails
|
|
|
43
233
|
.select { |collection| is_a?(collection[:options][:association_class]) }
|
|
44
234
|
.map { |collection| parent.collection_for(collection[:name], user_context: user_context) }
|
|
45
235
|
end
|
|
46
|
-
|
|
47
|
-
module ClassMethods
|
|
48
|
-
def collections_add(opts)
|
|
49
|
-
initialize_collections
|
|
50
|
-
collections.delete_if { |c| c[:name] == opts[:name] }
|
|
51
|
-
collections.append(opts)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def initialize_collections
|
|
55
|
-
return if collections && method(:collections).owner == singleton_class
|
|
56
|
-
|
|
57
|
-
self.collections = superclass.try(:collections)&.dup || []
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# Defines a collection to be used in {collection_for}
|
|
61
|
-
# @see Ldable#collection_for
|
|
62
|
-
# @note Adds a instance_method <name>_collection
|
|
63
|
-
# @param [Hash] name as to be used in {collection_for}
|
|
64
|
-
# @param [Hash] options
|
|
65
|
-
# @option options [Sym] association the name of the association
|
|
66
|
-
# @option options [Class] association_class the class of the association
|
|
67
|
-
# @option options [Sym] joins the associations to join
|
|
68
|
-
# @return [Collection]
|
|
69
|
-
def with_collection(name, options = {})
|
|
70
|
-
options[:association] ||= name.to_sym
|
|
71
|
-
options[:association_class] ||= name.to_s.classify.constantize
|
|
72
|
-
|
|
73
|
-
collections_add(name: name, options: options)
|
|
74
|
-
|
|
75
|
-
define_method "#{name.to_s.singularize}_collection" do |opts = {}|
|
|
76
|
-
collection_for(name, opts)
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
236
|
end
|
|
81
237
|
end
|
|
82
238
|
end
|