rdf-serializers 0.0.7 → 0.0.11

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