rdf-serializers 0.0.7 → 0.0.11

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a641b5b49c74bdc4479196b25f4c70e5718fabb324a703e10e4f60d07fb8574
4
- data.tar.gz: 45be5ccf9f007536f261332f396d65a8ecf8b2f63bd5ee9878320e50433a415c
3
+ metadata.gz: d375c9e57bfcdfcaf9bee6372abbf54ea974e1991a34936917e59c1ed67469a8
4
+ data.tar.gz: 0efed8bc77b7d97301d08ddaf5e5194c6234fd2c203bc1244d0269f06c81becf
5
5
  SHA512:
6
- metadata.gz: d908f4bf5f1886890c23e9268779aa4755b3de4f2e5b1e4910f2117c21983a21fef01bc32a8af24b89942b4e1b2fc46d46d0b0481fea52831d86a8b779d42c9a
7
- data.tar.gz: f9bdabf7f8455e09dd9ae273d565061c13f127f718cb271f9387cb442a0a5469daa4f0cda5b8058181f74d178c54ab4caae9ca6deb89f65f510a9dafae248d8f
6
+ metadata.gz: 6edcec3ce7990cb072e6211ce96452d12dd7c245de15177dffafac781b86e48b3f04e60e01af9853e8523a129cc84210c5e9ed3959a1c192c35184f7379a0f5f
7
+ data.tar.gz: 807766507554892c53f3dd0c9a3199a9d80b59f8b9c4ddb763b609d57727249f050797dfb55591837ce519b4b31fd3635be96fe14aae48e97dca72e01f997604
@@ -14,10 +14,34 @@ module RDF
14
14
  include ActiveSupport::Configurable
15
15
  config_accessor :always_include_named_graphs
16
16
  config_accessor :default_graph
17
+ config_accessor :serializer_lookup_chain
18
+ config_accessor :serializer_lookup_enabled
17
19
  end
18
20
 
19
21
  configure do |config|
20
22
  config.always_include_named_graphs = true
23
+
24
+ config.serializer_lookup_enabled = true
25
+
26
+ # For configuring how serializers are found.
27
+ # This should be an array of procs.
28
+ #
29
+ # The priority of the output is that the first item
30
+ # in the evaluated result array will take precedence
31
+ # over other possible serializer paths.
32
+ #
33
+ # i.e.: First match wins.
34
+ #
35
+ # @example output
36
+ # => [
37
+ # "CustomNamespace::ResourceSerializer",
38
+ # "ParentSerializer::ResourceSerializer",
39
+ # "ResourceNamespace::ResourceSerializer" ,
40
+ # "ResourceSerializer"]
41
+ #
42
+ # If CustomNamespace::ResourceSerializer exists, it will be used
43
+ # for serialization
44
+ config.serializer_lookup_chain = RDF::Serializers::LookupChain::DEFAULT.dup
21
45
  end
22
46
  end
23
47
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ module DataTypeHelper
6
+ def xsd_to_rdf(xsd, value, **opts) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
7
+ klass =
8
+ case xsd
9
+ when RDF::XSD[:anyURI]
10
+ RDF::URI
11
+ when RDF::XSD[:integer]
12
+ RDF::Literal::Integer
13
+ when RDF::XSD[:dateTime]
14
+ RDF::Literal::DateTime
15
+ when RDF::XSD[:date]
16
+ RDF::Literal::Date
17
+ when RDF::XSD[:boolean]
18
+ RDF::Literal::Boolean
19
+ when RDF::XSD[:time]
20
+ RDF::Literal::Time
21
+ when RDF::XSD[:long], RDF::XSD[:double]
22
+ RDF::Literal::Double
23
+ when RDF::XSD[:decimal]
24
+ RDF::Literal::Decimal
25
+ when RDF::XSD[:token]
26
+ RDF::Literal::Token
27
+ else
28
+ RDF::Literal
29
+ end
30
+
31
+ klass.new(value, **opts)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ class HndJSONParser
6
+ include DataTypeHelper
7
+
8
+ HEX_SUBJECT = 0
9
+ HEX_PREDICATE = 1
10
+ HEX_OBJECT = 2
11
+ HEX_DATATYPE = 3
12
+ HEX_LANGUAGE = 4
13
+ HEX_GRAPH = 5
14
+
15
+ def parse_body(body)
16
+ body.split("\n").map { |line| parse_hex(JSON.parse(line)) }
17
+ end
18
+
19
+ def parse_hex(hex)
20
+ subject = parse_subject(hex[HEX_SUBJECT])
21
+ predicate = RDF::URI(hex[HEX_PREDICATE])
22
+ object = parse_object(hex[HEX_OBJECT], hex[HEX_DATATYPE], hex[HEX_LANGUAGE])
23
+ graph = hex[HEX_GRAPH].present? ? RDF::URI(hex[HEX_GRAPH]) : RDF::Serializers.config.default_graph
24
+
25
+ RDF::Statement.new(
26
+ subject,
27
+ predicate,
28
+ object,
29
+ graph_name: graph
30
+ )
31
+ end
32
+
33
+ private
34
+
35
+ def blank_node(id)
36
+ @blank_nodes ||= {}
37
+ @blank_nodes[id] ||= RDF::Node(id)
38
+ end
39
+
40
+ def parse_object(value, datatype, language)
41
+ case datatype
42
+ when 'http://www.w3.org/1999/02/22-rdf-syntax-ns#namedNode'
43
+ RDF::URI(value)
44
+ when 'http://www.w3.org/1999/02/22-rdf-syntax-ns#blankNode'
45
+ blank_node(value.sub('_:', ''))
46
+ when language
47
+ RDF::Literal(value, datatype: RDF.langString, language: language)
48
+ else
49
+ xsd_to_rdf(datatype, value, language: language.presence)
50
+ end
51
+ end
52
+
53
+ def parse_subject(subject)
54
+ if subject.is_a?(RDF::Resource)
55
+ subject
56
+ elsif subject.start_with?('_')
57
+ blank_node(subject.sub('_:', ''))
58
+ else
59
+ RDF::URI(subject)
60
+ end
61
+ end
62
+
63
+ class << self
64
+ def parse(body)
65
+ new.parse_body(body)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ module HextupleSerializer
6
+ def iri_from_record(record)
7
+ return record if record.try(:uri?)
8
+
9
+ raise FastJsonapi::MandatoryField, 'record has no iri' unless record.respond_to?(:iri)
10
+
11
+ record.iri
12
+ end
13
+
14
+ def normalized_object(object)
15
+ case object
16
+ when ::RDF::Term
17
+ object
18
+ when ActiveSupport::TimeWithZone
19
+ ::RDF::Literal(object.to_datetime)
20
+ else
21
+ ::RDF::Literal(object)
22
+ end
23
+ end
24
+
25
+ def object_value(obj)
26
+ if obj.is_a?(::RDF::URI)
27
+ obj.value
28
+ elsif obj.is_a?(::RDF::Node)
29
+ obj.to_s
30
+ else
31
+ obj.value.to_s
32
+ end
33
+ end
34
+
35
+ def object_datatype(obj)
36
+ if obj.is_a?(::RDF::URI)
37
+ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#namedNode'
38
+ elsif obj.is_a?(::RDF::Node)
39
+ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#blankNode'
40
+ elsif obj.try(:datatype?)
41
+ obj.datatype
42
+ else
43
+ lit = RDF::Literal(obj)
44
+ lit.datatype.to_s
45
+ end
46
+ end
47
+
48
+ def value_to_hex(iri, predicate, object, graph = nil, serialization_params = {})
49
+ return if object.nil?
50
+
51
+ obj = normalized_object(object)
52
+
53
+ [
54
+ iri.to_s,
55
+ predicate.to_s,
56
+ object_value(obj),
57
+ object_datatype(obj),
58
+ obj.try(:language) || '',
59
+ operation((graph || ::RDF::Serializers.config.default_graph)&.value, serialization_params[:context])
60
+ ]
61
+ end
62
+
63
+ def operation(operation, graph_name)
64
+ return nil if operation.blank?
65
+ return operation if graph_name.blank?
66
+
67
+ "#{operation}?graph=#{WEBrick::HTTPUtils.escape_form(graph_name.to_s)}"
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ class ListSerializer
6
+ include RDF::Serializers::ObjectSerializer
7
+
8
+ def hextuples_for_collection
9
+ @resource.map do |resource|
10
+ RDF::Serializers.serializer_for(resource).record_hextuples(resource, nil, @includes, @params)
11
+ end.flatten(1)
12
+ end
13
+
14
+ class << self
15
+ def validate_includes!(_includes); end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ # Extracted from active_model_serializers
6
+ module LookupChain
7
+ # Standard appending of Serializer to the resource name.
8
+ #
9
+ # Example:
10
+ # Author => AuthorSerializer
11
+ BY_RESOURCE = lambda do |resource_class, _namespace|
12
+ serializer_from(resource_class)
13
+ end
14
+
15
+ # Uses the namespace of the resource to find the serializer
16
+ #
17
+ # Example:
18
+ # British::Author => British::AuthorSerializer
19
+ BY_RESOURCE_NAMESPACE = lambda do |resource_class, _namespace|
20
+ resource_namespace = namespace_for(resource_class)
21
+ serializer_name = serializer_from(resource_class)
22
+
23
+ "#{resource_namespace}::#{serializer_name}" if resource_namespace && serializer_name
24
+ end
25
+
26
+ # Uses the controller namespace of the resource to find the serializer
27
+ #
28
+ # Example:
29
+ # Api::V3::AuthorsController => Api::V3::AuthorSerializer
30
+ BY_NAMESPACE = lambda do |resource_class, namespace|
31
+ (namespace && resource_class.name) ? "#{namespace}::#{resource_class_name(resource_class)}Serializer" : nil
32
+ end
33
+
34
+ DEFAULT = [
35
+ BY_NAMESPACE,
36
+ BY_RESOURCE_NAMESPACE,
37
+ BY_RESOURCE
38
+ ].freeze
39
+
40
+ module_function
41
+
42
+ def namespace_for(klass)
43
+ klass.name&.deconstantize
44
+ end
45
+
46
+ def resource_class_name(klass)
47
+ klass.name&.demodulize
48
+ end
49
+
50
+ def serializer_from_resource_name(name)
51
+ "#{name}Serializer"
52
+ end
53
+
54
+ def serializer_from(klass)
55
+ name = resource_class_name(klass)
56
+ serializer_from_resource_name(name) if name
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ class NilSerializer
6
+ include RDF::Serializers::ObjectSerializer
7
+
8
+ class << self
9
+ def validate_includes!(_includes); end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'oj'
4
+
5
+ module RDF
6
+ module Serializers
7
+ module ObjectSerializer
8
+ extend ActiveSupport::Concern
9
+ include FastJsonapi::ObjectSerializer
10
+ include SerializationCore
11
+
12
+ included do
13
+ class_attribute :_statements
14
+ self._statements ||= []
15
+ end
16
+
17
+ def dump(*args, **options)
18
+ case args.first
19
+ when :hndjson
20
+ render_hndjson
21
+ else
22
+ render_repository(*args, **options)
23
+ end
24
+ end
25
+
26
+ def triples(*args, **options)
27
+ if include_named_graphs?(*args)
28
+ repository.triples(*args, **options)
29
+ else
30
+ repository.project_graph(nil).triples(*args, **options)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def blank_node(id)
37
+ @blank_nodes ||= {}
38
+ @blank_nodes[id] ||= RDF::Node(id)
39
+ end
40
+
41
+ def hextuples_for_collection
42
+ data = []
43
+ fieldset = @fieldsets[self.class.record_type.to_sym]
44
+ @resource.each do |record|
45
+ data.concat self.class.record_hextuples(record, fieldset, @includes, @params)
46
+ next unless @includes.present?
47
+
48
+ data.concat(
49
+ self.class.get_included_records_hex(record, @includes, @known_included_objects, @fieldsets, @params)
50
+ )
51
+ end
52
+
53
+ data
54
+ end
55
+
56
+ def hextuples_for_one_record
57
+ serializable_hextuples = []
58
+
59
+ serializable_hextuples.concat self.class.record_hextuples(
60
+ @resource,
61
+ @fieldsets[self.class.record_type.to_sym],
62
+ @includes,
63
+ @params
64
+ )
65
+
66
+ if @includes.present?
67
+ serializable_hextuples.concat self.class.get_included_records_hex(
68
+ @resource,
69
+ @includes,
70
+ @known_included_objects,
71
+ @fieldsets,
72
+ @params
73
+ )
74
+ end
75
+
76
+ serializable_hextuples
77
+ end
78
+
79
+ def include_named_graphs?(*args)
80
+ ::RDF::Serializers.config.always_include_named_graphs ||
81
+ ::RDF::Writer.for(*args.presence || :nquads).instance_methods.include?(:write_quad)
82
+ end
83
+
84
+ def meta_hextuples
85
+ return [] unless @meta.is_a?(Array)
86
+
87
+ @meta.map do |statement|
88
+ if statement.is_a?(Array)
89
+ value_to_hex(statement[0], statement[1], statement[2], statement[3], @params)
90
+ else
91
+ value_to_hex(statement.subject.to_s, statement.predicate, statement.object, statement.graph_name, @params)
92
+ end
93
+ end.compact
94
+ end
95
+
96
+ def repository
97
+ return @repository if @repository.present?
98
+
99
+ @repository = ::RDF::Repository.new
100
+ parser = HndJSONParser.new
101
+
102
+ serializable_hextuples.compact.each do |hextuple|
103
+ @repository << parser.parse_hex(hextuple)
104
+ end
105
+
106
+ @repository
107
+ end
108
+
109
+ def render_hndjson
110
+ serializable_hextuples
111
+ .map { |s| Oj.fast_generate(s) }
112
+ .join("\n")
113
+ end
114
+
115
+ def render_repository(*args, **options)
116
+ if include_named_graphs?(*args)
117
+ repository.dump(*args, **options)
118
+ else
119
+ repository.project_graph(nil).dump(*args, **options)
120
+ end
121
+ end
122
+
123
+ def serializable_hextuples
124
+ if self.class.is_collection?(@resource, @is_collection)
125
+ hextuples_for_collection + meta_hextuples
126
+ elsif !@resource
127
+ []
128
+ else
129
+ hextuples_for_one_record + meta_hextuples
130
+ end
131
+ end
132
+
133
+ class_methods do
134
+ def create_relationship(base_key, relationship_type, options, block)
135
+ association = options.delete(:association)
136
+ image = options.delete(:image)
137
+ predicate = options.delete(:predicate)
138
+ sequence = options.delete(:sequence)
139
+ relation = super
140
+ relation.association = association
141
+ relation.image = image
142
+ relation.predicate = predicate
143
+ relation.sequence = sequence
144
+
145
+ relation
146
+ end
147
+
148
+ # Checks for the `class_name` property on the Model's association to
149
+ # determine a serializer.
150
+ def association_serializer_for(name)
151
+ model_class_name = self.name.to_s.demodulize.classify.gsub(/Serializer$/, '')
152
+ model_class = model_class_name.safe_constantize
153
+
154
+ association_class_name = model_class.try(:reflect_on_association, name)&.class_name
155
+
156
+ return nil unless association_class_name
157
+
158
+ serializer_for(association_class_name)
159
+ end
160
+
161
+ def inherited(base)
162
+ super
163
+ base._statements = _statements.dup
164
+ end
165
+
166
+ def serializer_for(name)
167
+ associatopm_serializer = association_serializer_for(name)
168
+ return associatopm_serializer if associatopm_serializer
169
+
170
+ begin
171
+ RDF::Serializers.serializer_for(const_get(name.to_s.classify))
172
+ rescue NameError
173
+ raise NameError, "#{self.name} cannot resolve a serializer class for '#{name}'. " \
174
+ 'Consider specifying the serializer directly through options[:serializer].'
175
+ end
176
+ end
177
+
178
+ def statements(attribute)
179
+ self._statements << attribute
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ module Relationship
6
+ include HextupleSerializer
7
+
8
+ attr_accessor :predicate, :image, :association, :sequence
9
+
10
+ def serialize_hex(record, included, serialization_params)
11
+ return [] unless include_relationship?(record, serialization_params, included) && predicate.present?
12
+
13
+ iris = iris_from_record_and_relationship(record, serialization_params)
14
+
15
+ sequence ? relationship_sequence(record, iris, serialization_params) : relationship_statements(record, iris, serialization_params)
16
+ end
17
+
18
+ def relationship_sequence(record, iris, serialization_params)
19
+ return [] if iris.blank?
20
+
21
+ sequence = RDF::Node.new
22
+
23
+ [
24
+ value_to_hex(iri_from_record(record).to_s, predicate, sequence, nil, serialization_params),
25
+ value_to_hex(sequence, RDF.type, RDF.Seq, nil, serialization_params)
26
+ ] + iris.map.with_index do |iri, index|
27
+ value_to_hex(sequence, RDF["_#{index}"], iri, nil, serialization_params)
28
+ end
29
+ end
30
+
31
+ def relationship_statements(record, iris, serialization_params)
32
+ iris.map do |related_iri|
33
+ value_to_hex(
34
+ iri_from_record(record).to_s,
35
+ predicate,
36
+ related_iri,
37
+ nil,
38
+ serialization_params
39
+ )
40
+ end
41
+ end
42
+
43
+ def include_relationship?(record, serialization_params, included = false)
44
+ return false if lazy_load_data && !included
45
+
46
+ super(record, serialization_params)
47
+ end
48
+
49
+ def iris_from_record_and_relationship(record, params = {})
50
+ initialize_static_serializer unless @initialized_static_serializer
51
+
52
+ associated_object = fetch_associated_object(record, params)
53
+ return [] unless associated_object
54
+
55
+ if associated_object.respond_to? :map
56
+ return associated_object.compact.map do |object|
57
+ iri_from_record(object)
58
+ end
59
+ end
60
+
61
+ [iri_from_record(associated_object)]
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ FastJsonapi::Relationship.prepend(RDF::Serializers::Relationship)
@@ -19,16 +19,42 @@ module RDF
19
19
  raise "#{symbol} is not a known rdf format" if format.nil?
20
20
 
21
21
  Mime::Type.register format.content_type.first, format.file_extension.first
22
- add_renderer(format, opts)
22
+ add_renderer(format.file_extension.first, format.content_type.first, format.symbols.first, opts)
23
23
  end
24
24
  end
25
25
 
26
- def self.add_renderer(format, opts = {})
27
- ActionController::Renderers.add format.file_extension.first do |resource, options|
28
- self.content_type = format.content_type.first
29
- get_serializer(resource, options.merge(adapter: :rdf)).adapter.dump(format.symbols.first, opts)
26
+ def self.add_renderer(ext, content_type, symbol, opts = {})
27
+ ActionController::Renderers.add ext do |resource, options|
28
+ self.content_type = content_type
29
+ serializer_opts = RDF::Serializers::Renderers.transform_opts(
30
+ options,
31
+ respond_to?(:serializer_params, true) ? serializer_params : {}
32
+ )
33
+ RDF::Serializers.serializer_for(resource)&.new(resource, serializer_opts)&.dump(symbol, **opts)
30
34
  end
31
35
  end
36
+
37
+ def self.transform_include(include, root = nil)
38
+ return root if include.blank?
39
+ return [root, include].compact.join('.') if include.is_a?(Symbol) || include.is_a?(String)
40
+
41
+ if include.is_a?(Hash)
42
+ include.flat_map do |k, v|
43
+ transform_include(v, [root, k].compact.join('.'))
44
+ end
45
+ elsif include.is_a?(Array)
46
+ include.flat_map do |v|
47
+ transform_include(v, root)
48
+ end
49
+ end.compact
50
+ end
51
+
52
+ def self.transform_opts(options, params)
53
+ (options || {}).merge(
54
+ include: transform_include(options[:include]),
55
+ params: params
56
+ )
57
+ end
32
58
  end
33
59
  end
34
60
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ module Scalar
6
+ include HextupleSerializer
7
+
8
+ attr_accessor :predicate, :image, :datatype
9
+
10
+ def initialize(key:, method:, options: {})
11
+ super
12
+ @predicate = options[:predicate]
13
+ @image = options[:image]
14
+ @datatype = options[:datatype]
15
+ end
16
+
17
+ def serialize_hex(record, serialization_params)
18
+ return [] unless conditionally_allowed?(record, serialization_params) && predicate.present?
19
+
20
+ value = value_from_record(record, method, serialization_params)
21
+
22
+ return [] if value.nil?
23
+
24
+ if value.is_a?(Array)
25
+ value.map { |arr_item| value_to_hex(iri_from_record(record).to_s, predicate, arr_item, nil, serialization_params) }
26
+ elsif value.is_a?(::RDF::List)
27
+ first = value.statements.first&.subject || RDF.nil
28
+ value.statements.map do |statement|
29
+ value_to_hex(statement.subject.to_s, statement.predicate, statement.object, statement.graph_name, serialization_params)
30
+ end + [
31
+ value_to_hex(iri_from_record(record).to_s, predicate, first, nil, serialization_params)
32
+ ]
33
+ else
34
+ [value_to_hex(iri_from_record(record).to_s, predicate, value, nil, serialization_params)]
35
+ end
36
+ end
37
+
38
+ def value_from_record(record, method, serialization_params)
39
+ if method.is_a?(Proc)
40
+ FastJsonapi.call_proc(method, record, serialization_params)
41
+ else
42
+ v = record.public_send(method)
43
+ v.is_a?('ActiveRecord'.safe_constantize&.const_get('Relation') || NilClass) ? v.to_a : v
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ FastJsonapi::Scalar.prepend(RDF::Serializers::Scalar)
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ module SerializationCore
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include HextupleSerializer
10
+ extend HextupleSerializer
11
+ end
12
+
13
+ class_methods do
14
+ def relationships_hextuples(record, relationships = nil, fieldset = nil, includes_list = [], params = {})
15
+ relationships = relationships_to_serialize if relationships.nil?
16
+ relationships = relationships.slice(*fieldset) if fieldset.present?
17
+ relationships = [] if fieldset == []
18
+
19
+ statements = []
20
+ relationships.each do |key, relationship|
21
+ included = includes_list.present? && includes_list.include?(key)
22
+ statements.concat relationship.serialize_hex(record, included, params)
23
+ end
24
+
25
+ statements
26
+ end
27
+
28
+ def attributes_hextuples(record, fieldset = nil, params = {})
29
+ attributes = attributes_to_serialize
30
+ attributes = attributes.slice(*fieldset) if fieldset.present?
31
+ attributes = {} if fieldset == []
32
+
33
+ statements = attributes.flat_map do |_k, attr|
34
+ attr.serialize_hex(record, params)
35
+ end
36
+
37
+ statements.compact
38
+ end
39
+
40
+ def statements_hextuples(record, params = {})
41
+ statements = []
42
+
43
+ _statements&.map do |key|
44
+ send(key, record, params).each do |statement|
45
+ statements << if statement.is_a?(Array)
46
+ value_to_hex(statement[0], statement[1], statement[2], statement[3], params)
47
+ else
48
+ value_to_hex(statement.subject.to_s, statement.predicate, statement.object, statement.graph_name, params)
49
+ end
50
+ end
51
+ end
52
+
53
+ statements.compact
54
+ end
55
+
56
+ def record_hextuples(record, fieldset, includes_list, params = {})
57
+ if cache_store_instance
58
+ record_hex = Rails.cache.fetch(record.cache_key, expires_in: cache_length, race_condition_ttl: race_condition_ttl) do
59
+ temp_hex = []
60
+ temp_hex.concat attributes_hextuples(record, fieldset, params) if attributes_to_serialize.present?
61
+ temp_hex.concat statements_hextuples(record, params)
62
+ if cachable_relationships_to_serialize.present?
63
+ temp_hex.concat relationships_hextuples(record, cachable_relationships_to_serialize, fieldset, includes_list, params)
64
+ end
65
+ temp_hex
66
+ end
67
+ if uncachable_relationships_to_serialize.present?
68
+ record_hex.concat relationships_hextuples(record, uncachable_relationships_to_serialize, fieldset, includes_list, params)
69
+ end
70
+ else
71
+ record_hex = []
72
+ record_hex.concat attributes_hextuples(record, fieldset, params) if attributes_to_serialize.present?
73
+ record_hex.concat statements_hextuples(record, params)
74
+ if relationships_to_serialize.present?
75
+ record_hex.concat relationships_hextuples(record, nil, fieldset, includes_list, params)
76
+ end
77
+ end
78
+ record_hex
79
+ end
80
+
81
+ def get_included_records_hex(record, includes_list, known_included_objects, fieldsets, params = {})
82
+ return unless includes_list.present?
83
+ return [] unless relationships_to_serialize
84
+
85
+ includes_list = parse_includes_list(includes_list)
86
+
87
+ includes_list.each_with_object([]) do |include_item, included_records|
88
+ relationship_item = relationships_to_serialize[include_item.first]
89
+
90
+ next unless relationship_item&.include_relationship?(record, params)
91
+
92
+ included_objects = Array(relationship_item.fetch_associated_object(record, params))
93
+ next if included_objects.empty?
94
+
95
+ static_serializer = relationship_item.static_serializer
96
+ static_record_type = relationship_item.static_record_type
97
+
98
+ included_objects.each do |inc_obj|
99
+ serializer = static_serializer || relationship_item.serializer_for(inc_obj, params)
100
+ record_type = static_record_type || serializer.record_type
101
+
102
+ if include_item.last.any?
103
+ serializer_records = serializer.get_included_records_hex(inc_obj, include_item.last, known_included_objects, fieldsets, params)
104
+ included_records.concat(serializer_records) unless serializer_records.empty?
105
+ end
106
+
107
+ code = "#{record_type}_#{serializer.iri_from_record(inc_obj)}"
108
+ next if known_included_objects.include?(code)
109
+
110
+ known_included_objects << code
111
+
112
+ included_records.concat(
113
+ serializer.record_hextuples(inc_obj, fieldsets[record_type], includes_list, params)
114
+ )
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RDFSerializers
4
4
  module Version
5
- VERSION = '0.0.7'
5
+ VERSION = '0.0.11'
6
6
  end
7
7
  end
@@ -1,5 +1,95 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_model_serializers/adapter/rdf'
4
- require 'active_model_serializers/serializer'
3
+ require 'fast_jsonapi'
4
+
5
+ require 'rdf/serializers/lookup_chain'
5
6
  require 'rdf/serializers/config'
7
+ require 'rdf/serializers/hextuple_serializer'
8
+ require 'rdf/serializers/data_type_helper'
9
+ require 'rdf/serializers/hdnjson_parser'
10
+ require 'rdf/serializers/serialization_core'
11
+ require 'rdf/serializers/object_serializer'
12
+ require 'rdf/serializers/scalar'
13
+ require 'rdf/serializers/relationship'
14
+ require 'rdf/serializers/nil_serializer'
15
+ require 'rdf/serializers/list_serializer'
16
+
17
+ module RDF
18
+ module Serializers
19
+ class << self
20
+ # Extracted from active_model_serializers
21
+ # @param resource [ActiveRecord::Base, ActiveModelSerializers::Model]
22
+ # @return [ActiveModel::Serializer]
23
+ # Preferentially returns
24
+ # 1. resource.serializer_class
25
+ # 2. ArraySerializer when resource is a collection
26
+ # 3. options[:serializer]
27
+ # 4. lookup serializer when resource is a Class
28
+ def serializer_for(resource_or_class, options = {})
29
+ if resource_or_class.respond_to?(:serializer_class)
30
+ resource_or_class.serializer_class
31
+ elsif resource_or_class.respond_to?(:to_ary)
32
+ unless resource_or_class.all? { |resource| resource.is_a?(resource_or_class.first.class) }
33
+ return ListSerializer
34
+ end
35
+
36
+ serializer_for(resource_or_class.first)
37
+ else
38
+ resource_class = resource_or_class.class == Class ? resource_or_class : resource_or_class.class
39
+ options.fetch(:serializer) { get_serializer_for(resource_class, options[:namespace]) }
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ # Extracted from active_model_serializers
46
+ # Find a serializer from a class and caches the lookup.
47
+ # Preferentially returns:
48
+ # 1. class name appended with "Serializer"
49
+ # 2. try again with superclass, if present
50
+ # 3. nil
51
+ def get_serializer_for(klass, namespace = nil)
52
+ return nil unless config.serializer_lookup_enabled
53
+
54
+ return NilSerializer if klass == NilClass
55
+
56
+ cache_key = ActiveSupport::Cache.expand_cache_key(klass, namespace)
57
+ serializers_cache.fetch_or_store(cache_key) do
58
+ # NOTE(beauby): When we drop 1.9.3 support we can lazify the map for perfs.
59
+ lookup_chain = serializer_lookup_chain_for(klass, namespace)
60
+ serializer_class = serializer_class_from_chain(lookup_chain)
61
+
62
+ if serializer_class
63
+ serializer_class
64
+ elsif klass.superclass
65
+ get_serializer_for(klass.superclass, namespace)
66
+ end
67
+ end
68
+ end
69
+
70
+ # Extracted from active_model_serializers
71
+ # Used to cache serializer name => serializer class
72
+ # when looked up by Serializer.get_serializer_for.
73
+ def serializers_cache
74
+ @serializers_cache ||= Concurrent::Map.new
75
+ end
76
+
77
+ def serializer_class_from_chain(lookup_chain)
78
+ lookup_chain.map do |klass|
79
+ klass&.safe_constantize
80
+ rescue LoadError
81
+ nil
82
+ end.find do |klass|
83
+ klass&.include?(FastJsonapi::SerializationCore)
84
+ end
85
+ end
86
+
87
+ def serializer_lookup_chain_for(klass, namespace = nil)
88
+ lookups = config.serializer_lookup_chain
89
+ Array[*lookups].flat_map do |lookup|
90
+ lookup.call(klass, namespace)
91
+ end.compact
92
+ end
93
+ end
94
+ end
95
+ end
metadata CHANGED
@@ -1,29 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rdf-serializers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arthur Dingemans
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-28 00:00:00.000000000 Z
11
+ date: 2021-11-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: active_model_serializers
14
+ name: jsonapi-serializer
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0.10'
19
+ version: 2.1.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 2.1.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: oj
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
25
39
  - !ruby/object:Gem::Version
26
- version: '0.10'
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: railties
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -58,24 +72,31 @@ dependencies:
58
72
  - - "~>"
59
73
  - !ruby/object:Gem::Version
60
74
  version: '3.0'
61
- description:
75
+ description:
62
76
  email: arthur@argu.co
63
77
  executables: []
64
78
  extensions: []
65
79
  extra_rdoc_files: []
66
80
  files:
67
- - lib/active_model_serializers/adapter/rdf.rb
68
- - lib/active_model_serializers/adapter/rdf/relationship.rb
69
- - lib/active_model_serializers/serializer.rb
70
81
  - lib/rdf/serializers.rb
71
82
  - lib/rdf/serializers/config.rb
83
+ - lib/rdf/serializers/data_type_helper.rb
84
+ - lib/rdf/serializers/hdnjson_parser.rb
85
+ - lib/rdf/serializers/hextuple_serializer.rb
86
+ - lib/rdf/serializers/list_serializer.rb
87
+ - lib/rdf/serializers/lookup_chain.rb
88
+ - lib/rdf/serializers/nil_serializer.rb
89
+ - lib/rdf/serializers/object_serializer.rb
90
+ - lib/rdf/serializers/relationship.rb
72
91
  - lib/rdf/serializers/renderers.rb
92
+ - lib/rdf/serializers/scalar.rb
93
+ - lib/rdf/serializers/serialization_core.rb
73
94
  - lib/rdf/serializers/version.rb
74
95
  homepage: https://github.com/ontola/rdf-serializers
75
96
  licenses:
76
97
  - MIT
77
98
  metadata: {}
78
- post_install_message:
99
+ post_install_message:
79
100
  rdoc_options: []
80
101
  require_paths:
81
102
  - lib
@@ -90,8 +111,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
111
  - !ruby/object:Gem::Version
91
112
  version: '0'
92
113
  requirements: []
93
- rubygems_version: 3.0.1
94
- signing_key:
114
+ rubygems_version: 3.2.22
115
+ signing_key:
95
116
  specification_version: 4
96
117
  summary: Adds RDF serialization, like n-triples or turtle, to active model serializers
97
118
  test_files: []
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveModelSerializers
4
- module Adapter
5
- class RDF
6
- class Relationship < JsonApi::Relationship
7
- def triples
8
- return [] if no_data?
9
-
10
- data.map do |object|
11
- raise "#{object} is not a RDF::Resource but a #{object.class}" unless object.is_a?(::RDF::Resource)
12
-
13
- ::RDF::Statement.new(subject, predicate, object, graph_name: graph_name)
14
- end
15
- end
16
-
17
- private
18
-
19
- def data
20
- @data ||=
21
- if association.collection?
22
- objects_for_many(association).compact
23
- else
24
- [object_for_one(association)].compact
25
- end
26
- end
27
-
28
- def graph_name
29
- association.reflection.options[:graph] || ::RDF::Serializers.config.default_graph
30
- end
31
-
32
- def no_data?
33
- subject.blank? || predicate.blank? || data.empty?
34
- end
35
-
36
- def objects_for_many(association)
37
- collection_serializer = association.lazy_association.serializer
38
- if collection_serializer.respond_to?(:each)
39
- collection_serializer.map do |serializer|
40
- serializer.read_attribute_for_serialization(:rdf_subject)
41
- end
42
- else
43
- []
44
- end
45
- end
46
-
47
- def object_for_one(association)
48
- if belongs_to_id_on_self?(association)
49
- parent_serializer.read_attribute_for_serialization(:rdf_subject)
50
- else
51
- serializer = association.lazy_association.serializer
52
- if (virtual_value = association.virtual_value)
53
- virtual_value[:id]
54
- elsif serializer && association.object
55
- serializer.read_attribute_for_serialization(:rdf_subject)
56
- end
57
- end
58
- end
59
-
60
- def predicate
61
- @predicate ||= association.reflection.options[:predicate]
62
- end
63
-
64
- def subject
65
- @subject ||= parent_serializer.read_attribute_for_serialization(:rdf_subject)
66
- end
67
- end
68
- end
69
- end
70
- end
@@ -1,193 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # rubocop:disable Metrics/ClassLength
4
- module ActiveModelSerializers
5
- module Adapter
6
- class RDF < Base
7
- extend ActiveSupport::Autoload
8
- autoload :Relationship
9
-
10
- delegate :object, to: :serializer
11
-
12
- def initialize(serializer, options = {})
13
- super
14
- @include_directive = JSONAPI::IncludeDirective.new(options[:include], allow_wildcard: true)
15
- @fieldset = options[:fieldset] || ActiveModel::Serializer::Fieldset.new(options.delete(:fields))
16
- @resource_identifiers = Set.new
17
- end
18
-
19
- def dump(*args, **options)
20
- if include_named_graphs?(*args)
21
- repository.dump(*args, options)
22
- else
23
- repository.project_graph(nil).dump(*args, options)
24
- end
25
- end
26
-
27
- def triples(*args, **options)
28
- if include_named_graphs?(*args)
29
- repository.triples(*args, options)
30
- else
31
- repository.project_graph(nil).triples(*args, options)
32
- end
33
- end
34
-
35
- protected
36
-
37
- attr_reader :fieldset
38
-
39
- private
40
-
41
- def add_attribute(subject, predicate, value, graph)
42
- return unless predicate
43
-
44
- normalized = value.is_a?(Array) ? value : [value]
45
- normalized.compact.map { |v| add_triple([subject, predicate, v, graph]) }
46
- end
47
-
48
- def add_triple(triple)
49
- @repository << (triple.is_a?(Array) ? normalized_triple(*triple) : triple)
50
- end
51
-
52
- def attributes_for(serializer, fields)
53
- serializer.attributes(fields).each do |key, value|
54
- add_attribute(
55
- serializer.read_attribute_for_serialization(:rdf_subject),
56
- serializer.class._attributes_data[key].try(:options).try(:[], :predicate),
57
- value,
58
- serializer.class._attributes_data[key].try(:options).try(:[], :graph)
59
- )
60
- end
61
- end
62
-
63
- def custom_triples_for(serializer)
64
- serializer.class.try(:_triples)&.map do |key|
65
- serializer.read_attribute_for_serialization(key).each do |triple|
66
- add_triple(triple)
67
- end
68
- end
69
- end
70
-
71
- def repository
72
- return @repository if @repository.present?
73
-
74
- @repository = ::RDF::Repository.new
75
-
76
- serializers.each { |serializer| process_resource(serializer, @include_directive) }
77
- serializers.each { |serializer| process_relationships(serializer, @include_directive) }
78
- instance_options[:meta]&.each { |meta| add_triple(meta) }
79
-
80
- raise_missing_nodes if raise_on_missing_nodes?
81
-
82
- @repository
83
- end
84
-
85
- def include_named_graphs?(*args)
86
- ::RDF::Serializers.config.always_include_named_graphs ||
87
- ::RDF::Writer.for(*args.presence || :nquads).instance_methods.include?(:write_quad)
88
- end
89
-
90
- def missing_nodes
91
- @missing_nodes ||=
92
- @repository
93
- .objects
94
- .select(&:node?)
95
- .reject { |n| @repository.has_subject?(n) }
96
- .map { |n| @repository.query([nil, nil, n]).first }
97
- end
98
-
99
- def normalized_object(object) # rubocop:disable Metrics/MethodLength
100
- case object
101
- when ::RDF::Term
102
- object
103
- when ::RDF::List
104
- list = object.statements
105
- @repository << object.statements
106
- list.first.subject
107
- when ActiveSupport::TimeWithZone
108
- ::RDF::Literal(object.to_datetime)
109
- else
110
- ::RDF::Literal(object)
111
- end
112
- end
113
-
114
- def normalized_triple(subject, predicate, object, graph = nil)
115
- ::RDF::Statement.new(
116
- subject,
117
- ::RDF::URI(predicate),
118
- normalized_object(object),
119
- graph_name: graph || ::RDF::Serializers.config.default_graph
120
- )
121
- end
122
-
123
- def process_relationship(serializer, include_slice)
124
- return serializer.each { |s| process_relationship(s, include_slice) } if serializer.respond_to?(:each)
125
-
126
- return unless serializer&.object && process_resource(serializer, include_slice)
127
-
128
- process_relationships(serializer, include_slice)
129
- end
130
-
131
- def process_relationships(serializer, include_slice)
132
- return unless serializer.respond_to?(:associations)
133
-
134
- serializer.associations(include_slice).each do |association|
135
- process_relationship(association.lazy_association.serializer, include_slice[association.key])
136
- end
137
- end
138
-
139
- def process_resource(serializer, include_slice = {})
140
- if serializer.is_a?(ActiveModel::Serializer::CollectionSerializer)
141
- return serializer.map { |child| process_resource(child, include_slice) }
142
- end
143
- return unless serializer.respond_to?(:rdf_subject) || serializer.object.respond_to?(:rdf_subject)
144
-
145
- return false unless @resource_identifiers.add?(serializer.read_attribute_for_serialization(:rdf_subject))
146
-
147
- resource_object_for(serializer, include_slice)
148
- true
149
- end
150
-
151
- def raise_on_missing_nodes?
152
- Rails.env.development? || Rails.env.test?
153
- end
154
-
155
- def raise_missing_nodes
156
- return if missing_nodes.empty?
157
-
158
- raise "The following triples point to nodes that are not included in the graph:\n#{missing_nodes.join("\n")}"
159
- end
160
-
161
- def relationships_for(serializer, requested_associations, include_slice)
162
- include_directive = JSONAPI::IncludeDirective.new(requested_associations, allow_wildcard: true)
163
- serializer.associations(include_directive, include_slice).each do |association|
164
- Relationship.new(serializer, instance_options, association).triples.each do |triple|
165
- add_triple(triple)
166
- end
167
- end
168
- end
169
-
170
- def resource_object_for(serializer, include_slice = {})
171
- type = type_for(serializer, instance_options).to_s
172
- serializer.fetch(self) do
173
- break nil if serializer.read_attribute_for_serialization(:rdf_subject).nil?
174
-
175
- requested_fields = fieldset&.fields_for(type)
176
- attributes_for(serializer, requested_fields)
177
- custom_triples_for(serializer)
178
- end
179
- requested_associations = fieldset.fields_for(type) || '*'
180
- relationships_for(serializer, requested_associations, include_slice)
181
- end
182
-
183
- def serializers
184
- serializer.respond_to?(:each) ? serializer : [serializer]
185
- end
186
-
187
- def type_for(serializer, instance_options)
188
- JsonApi::ResourceIdentifier.new(serializer, instance_options).as_json[:type]
189
- end
190
- end
191
- end
192
- end
193
- # rubocop:enable Metrics/ClassLength
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveModel
4
- class Serializer
5
- class_attribute :_triples
6
-
7
- def self.triples(attribute)
8
- self._triples ||= []
9
- self._triples << attribute
10
- end
11
- end
12
- end