rdf-serializers 0.0.4 → 0.0.8

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
- SHA1:
3
- metadata.gz: baf9653c33444a315a892a3720aa0be0503540db
4
- data.tar.gz: ca4fbe6d2ad6b87d3fabd8f1b68167fd0470475b
2
+ SHA256:
3
+ metadata.gz: 3782c211f6d60abc17cf58d6c0566c94913f5429d19f8c7ba633687dabf95ea8
4
+ data.tar.gz: c2cd93434a13082bd8d8da0b15b0d4554d7215b77e4e89ae2cf2fbeacb566957
5
5
  SHA512:
6
- metadata.gz: 06fda50c2eaec5b6bb346e31f40d78d24645211c3d87eef0c6206492d47f481cbde8a0a540ae447a0fdc75a6e31be45e9686bdfd252121f7bb998aa89c7db9ac
7
- data.tar.gz: 94a3b7701bd3291a3cff825782ff06945d1382336574de174030991f2c3f20558537c2ce6423fc8d8fd90c460094f3579c6f2d889abdf2af90c2215691ac8a2a
6
+ metadata.gz: c4938923d6d9bcb9b41e4c774025786dddf554dd8089691604e24235fff5311b8cb9cc39382a3e808d3aedd7909ee7e38fd1c273526aa469699413807cc6311f
7
+ data.tar.gz: 0a4131a7e8e1054620945780c4a430e33c846f78f2eedf450a881cfb2452ffbbd852f58c765540bbf329791b6614f40afe2c797d6f942f4021eca882284425aa
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ def self.configure
6
+ yield @config ||= RDF::Serializers::Configuration.new
7
+ end
8
+
9
+ def self.config
10
+ @config
11
+ end
12
+
13
+ class Configuration
14
+ include ActiveSupport::Configurable
15
+ config_accessor :always_include_named_graphs
16
+ config_accessor :default_graph
17
+ config_accessor :serializer_lookup_chain
18
+ config_accessor :serializer_lookup_enabled
19
+ end
20
+
21
+ configure do |config|
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
45
+ end
46
+ end
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,61 @@
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}"
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
+ resource_name = resource_class_name(resource_class)
32
+ namespace ? "#{namespace}::#{resource_name}Serializer" : nil
33
+ end
34
+
35
+ DEFAULT = [
36
+ BY_NAMESPACE,
37
+ BY_RESOURCE_NAMESPACE,
38
+ BY_RESOURCE
39
+ ].freeze
40
+
41
+ module_function
42
+
43
+ def namespace_for(klass)
44
+ klass.name.deconstantize
45
+ end
46
+
47
+ def resource_class_name(klass)
48
+ klass.name.demodulize
49
+ end
50
+
51
+ def serializer_from_resource_name(name)
52
+ "#{name}Serializer"
53
+ end
54
+
55
+ def serializer_from(klass)
56
+ name = resource_class_name(klass)
57
+ serializer_from_resource_name(name)
58
+ end
59
+ end
60
+ end
61
+ 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,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ module ObjectSerializer
6
+ extend ActiveSupport::Concern
7
+ include FastJsonapi::ObjectSerializer
8
+ include SerializationCore
9
+
10
+ def dump(*args, **options)
11
+ case args.first
12
+ when :hndjson
13
+ render_hndjson
14
+ else
15
+ render_repository(*args, options)
16
+ end
17
+ end
18
+
19
+ def triples(*args, **options)
20
+ if include_named_graphs?(*args)
21
+ repository.triples(*args, options)
22
+ else
23
+ repository.project_graph(nil).triples(*args, options)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def blank_node(id)
30
+ @blank_nodes ||= {}
31
+ @blank_nodes[id] ||= RDF::Node(id)
32
+ end
33
+
34
+ def hextuples_for_collection
35
+ data = []
36
+ fieldset = @fieldsets[self.class.record_type.to_sym]
37
+ @resource.each do |record|
38
+ data.concat self.class.record_hextuples(record, fieldset, @includes, @params)
39
+ next unless @includes.present?
40
+
41
+ data.concat(
42
+ self.class.get_included_records_hex(record, @includes, @known_included_objects, @fieldsets, @params)
43
+ )
44
+ end
45
+
46
+ data
47
+ end
48
+
49
+ def hextuples_for_one_record
50
+ serializable_hextuples = []
51
+
52
+ serializable_hextuples.concat self.class.record_hextuples(
53
+ @resource,
54
+ @fieldsets[self.class.record_type.to_sym],
55
+ @includes,
56
+ @params
57
+ )
58
+
59
+ if @includes.present?
60
+ serializable_hextuples.concat self.class.get_included_records_hex(
61
+ @resource,
62
+ @includes,
63
+ @known_included_objects,
64
+ @fieldsets,
65
+ @params
66
+ )
67
+ end
68
+
69
+ serializable_hextuples
70
+ end
71
+
72
+ def include_named_graphs?(*args)
73
+ ::RDF::Serializers.config.always_include_named_graphs ||
74
+ ::RDF::Writer.for(*args.presence || :nquads).instance_methods.include?(:write_quad)
75
+ end
76
+
77
+ def meta_hextuples
78
+ return [] unless @meta.is_a?(Array)
79
+
80
+ @meta.map do |statement|
81
+ if statement.is_a?(Array)
82
+ value_to_hex(statement[0], statement[1], statement[2], statement[3], @params)
83
+ else
84
+ value_to_hex(statement.subject.to_s, statement.predicate, statement.object, statement.graph_name, @params)
85
+ end
86
+ end.compact
87
+ end
88
+
89
+ def repository
90
+ return @repository if @repository.present?
91
+
92
+ @repository = ::RDF::Repository.new
93
+ parser = HndJSONParser.new
94
+
95
+ serializable_hextuples.compact.each do |hextuple|
96
+ @repository << parser.parse_hex(hextuple)
97
+ end
98
+
99
+ @repository
100
+ end
101
+
102
+ def render_hndjson
103
+ serializable_hextuples
104
+ .map { |s| Oj.fast_generate(s) }
105
+ .join("\n")
106
+ end
107
+
108
+ def render_repository(*args, options)
109
+ if include_named_graphs?(*args)
110
+ repository.dump(*args, options)
111
+ else
112
+ repository.project_graph(nil).dump(*args, options)
113
+ end
114
+ end
115
+
116
+ def serializable_hextuples
117
+ if is_collection?(@resource, @is_collection)
118
+ hextuples_for_collection + meta_hextuples
119
+ elsif !@resource
120
+ []
121
+ else
122
+ hextuples_for_one_record + meta_hextuples
123
+ end
124
+ end
125
+
126
+ class_methods do
127
+ def create_relationship(base_key, relationship_type, options, block)
128
+ association = options.delete(:association)
129
+ image = options.delete(:image)
130
+ predicate = options.delete(:predicate)
131
+ sequence = options.delete(:sequence)
132
+ relation = super
133
+ relation.association = association
134
+ relation.image = image
135
+ relation.predicate = predicate
136
+ relation.sequence = sequence
137
+
138
+ relation
139
+ end
140
+
141
+ # Checks for the `class_name` property on the Model's association to
142
+ # determine a serializer.
143
+ def association_serializer_for(name)
144
+ model_class_name = self.name.to_s.demodulize.classify.gsub(/Serializer$/, '')
145
+ model_class = model_class_name.safe_constantize
146
+
147
+ association_class_name = model_class.try(:reflect_on_association, name)&.class_name
148
+
149
+ return nil unless association_class_name
150
+
151
+ serializer_for(association_class_name)
152
+ end
153
+
154
+ def serializer_for(name)
155
+ associatopm_serializer = association_serializer_for(name)
156
+ return associatopm_serializer if associatopm_serializer
157
+
158
+ begin
159
+ RDF::Serializers.serializer_for(const_get(name.to_s.classify))
160
+ rescue NameError
161
+ raise NameError, "#{self.name} cannot resolve a serializer class for '#{name}'. " \
162
+ 'Consider specifying the serializer directly through options[:serializer].'
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,65 @@
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
+ sequence = RDF::Node.new
20
+
21
+ [
22
+ value_to_hex(iri_from_record(record).to_s, predicate, sequence, nil, serialization_params),
23
+ value_to_hex(sequence, RDF.type, RDF.Seq, nil, serialization_params)
24
+ ] + iris.map.with_index do |iri, index|
25
+ value_to_hex(sequence, RDF["_#{index}"], iri, nil, serialization_params)
26
+ end
27
+ end
28
+
29
+ def relationship_statements(record, iris, serialization_params)
30
+ iris.map do |related_iri|
31
+ value_to_hex(
32
+ iri_from_record(record).to_s,
33
+ predicate,
34
+ related_iri,
35
+ nil,
36
+ serialization_params
37
+ )
38
+ end
39
+ end
40
+
41
+ def include_relationship?(record, serialization_params, included = false)
42
+ return false if lazy_load_data && !included
43
+
44
+ super(record, serialization_params)
45
+ end
46
+
47
+ def iris_from_record_and_relationship(record, params = {})
48
+ initialize_static_serializer unless @initialized_static_serializer
49
+
50
+ associated_object = fetch_associated_object(record, params)
51
+ return [] unless associated_object
52
+
53
+ if associated_object.respond_to? :map
54
+ return associated_object.compact.map do |object|
55
+ iri_from_record(object)
56
+ end
57
+ end
58
+
59
+ [iri_from_record(associated_object)]
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ FastJsonapi::Relationship.prepend(RDF::Serializers::Relationship)
@@ -16,18 +16,45 @@ module RDF
16
16
  symbols = [symbols] unless symbols.respond_to?(:each)
17
17
  symbols.each do |symbol|
18
18
  format = RDF::Format.for(symbol)
19
- raise "#{symbol} if not a known rdf format" if format.nil?
19
+ raise "#{symbol} is not a known rdf format" if format.nil?
20
+
20
21
  Mime::Type.register format.content_type.first, format.file_extension.first
21
- add_renderer(format, opts)
22
+ add_renderer(format.file_extension.first, format.content_type.first, format.symbols.first, opts)
22
23
  end
23
24
  end
24
25
 
25
- def self.add_renderer(format, opts = {})
26
- ActionController::Renderers.add format.file_extension.first do |resource, options|
27
- self.content_type = format.content_type.first
28
- 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)
29
34
  end
30
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
31
58
  end
32
59
  end
33
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,130 @@
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
+
84
+ includes_list.sort.each_with_object([]) do |include_item, included_records|
85
+ items = parse_include_item(include_item)
86
+ remaining_items = remaining_items(items)
87
+
88
+ items.each do |item|
89
+ next unless relationships_to_serialize && relationships_to_serialize[item]
90
+
91
+ relationship_item = relationships_to_serialize[item]
92
+ next unless relationship_item.include_relationship?(record, params)
93
+
94
+ relationship_type = relationship_item.relationship_type
95
+
96
+ included_objects = relationship_item.fetch_associated_object(record, params)
97
+ next if included_objects.blank?
98
+
99
+ included_objects = [included_objects] unless relationship_type == :has_many
100
+
101
+ static_serializer = relationship_item.static_serializer
102
+ static_record_type = relationship_item.static_record_type
103
+
104
+ included_objects.each do |inc_obj|
105
+ serializer = static_serializer || relationship_item.serializer_for(inc_obj, params)
106
+ record_type = static_record_type || serializer.record_type
107
+
108
+ if remaining_items.present?
109
+ serializer_records =
110
+ serializer
111
+ .get_included_records_hex(inc_obj, remaining_items, known_included_objects, fieldsets, params)
112
+ included_records.concat(serializer_records) unless serializer_records.empty?
113
+ end
114
+
115
+ code = "#{record_type}_#{serializer.iri_from_record(inc_obj)}"
116
+ next if known_included_objects.key?(code)
117
+
118
+ known_included_objects[code] = inc_obj
119
+
120
+ included_records.concat(
121
+ serializer.record_hextuples(inc_obj, fieldsets[record_type], includes_list, params)
122
+ )
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RDF
4
+ module Serializers
5
+ module Statements
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ class_attribute :_statements
10
+ self._statements ||= []
11
+ end
12
+
13
+ module ClassMethods
14
+ def inherited(base)
15
+ super
16
+ base._statements = _statements.dup
17
+ end
18
+
19
+ def statements(attribute)
20
+ self._statements << attribute
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ FastJsonapi::ObjectSerializer.include(RDF::Serializers::Statements)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RDFSerializers
4
4
  module Version
5
- VERSION = '0.0.4'
5
+ VERSION = '0.0.8'
6
6
  end
7
7
  end
@@ -1,4 +1,88 @@
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'
6
+ require 'rdf/serializers/config'
7
+ require 'rdf/serializers/statements'
8
+ require 'rdf/serializers/hextuple_serializer'
9
+ require 'rdf/serializers/data_type_helper'
10
+ require 'rdf/serializers/hdnjson_parser'
11
+ require 'rdf/serializers/serialization_core'
12
+ require 'rdf/serializers/object_serializer'
13
+ require 'rdf/serializers/scalar'
14
+ require 'rdf/serializers/relationship'
15
+ require 'rdf/serializers/nil_serializer'
16
+ require 'rdf/serializers/list_serializer'
17
+
18
+ module RDF
19
+ module Serializers
20
+ class << self
21
+ # Extracted from active_model_serializers
22
+ # @param resource [ActiveRecord::Base, ActiveModelSerializers::Model]
23
+ # @return [ActiveModel::Serializer]
24
+ # Preferentially returns
25
+ # 1. resource.serializer_class
26
+ # 2. ArraySerializer when resource is a collection
27
+ # 3. options[:serializer]
28
+ # 4. lookup serializer when resource is a Class
29
+ def serializer_for(resource_or_class, options = {})
30
+ if resource_or_class.respond_to?(:serializer_class)
31
+ resource_or_class.serializer_class
32
+ elsif resource_or_class.respond_to?(:to_ary)
33
+ unless resource_or_class.all? { |resource| resource.is_a?(resource_or_class.first.class) }
34
+ return ListSerializer
35
+ end
36
+
37
+ serializer_for(resource_or_class.first)
38
+ else
39
+ resource_class = resource_or_class.class == Class ? resource_or_class : resource_or_class.class
40
+ options.fetch(:serializer) { get_serializer_for(resource_class, options[:namespace]) }
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ # Extracted from active_model_serializers
47
+ # Find a serializer from a class and caches the lookup.
48
+ # Preferentially returns:
49
+ # 1. class name appended with "Serializer"
50
+ # 2. try again with superclass, if present
51
+ # 3. nil
52
+ def get_serializer_for(klass, namespace = nil)
53
+ return nil unless config.serializer_lookup_enabled
54
+
55
+ return NilSerializer if klass == NilClass
56
+
57
+ cache_key = ActiveSupport::Cache.expand_cache_key(klass, namespace)
58
+ serializers_cache.fetch_or_store(cache_key) do
59
+ # NOTE(beauby): When we drop 1.9.3 support we can lazify the map for perfs.
60
+ lookup_chain = serializer_lookup_chain_for(klass, namespace)
61
+ serializer_class = lookup_chain.map(&:safe_constantize).find do |x|
62
+ x&.include?(FastJsonapi::SerializationCore)
63
+ end
64
+
65
+ if serializer_class
66
+ serializer_class
67
+ elsif klass.superclass
68
+ get_serializer_for(klass.superclass, namespace)
69
+ end
70
+ end
71
+ end
72
+
73
+ # Extracted from active_model_serializers
74
+ # Used to cache serializer name => serializer class
75
+ # when looked up by Serializer.get_serializer_for.
76
+ def serializers_cache
77
+ @serializers_cache ||= Concurrent::Map.new
78
+ end
79
+
80
+ def serializer_lookup_chain_for(klass, namespace = nil)
81
+ lookups = config.serializer_lookup_chain
82
+ Array[*lookups].flat_map do |lookup|
83
+ lookup.call(klass, namespace)
84
+ end.compact
85
+ end
86
+ end
87
+ end
88
+ end
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rdf-serializers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.8
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: 2017-12-11 00:00:00.000000000 Z
11
+ date: 2021-08-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: active_model_serializers
14
+ name: fast_jsonapi
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0.10'
19
+ version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '0.10'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: railties
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -33,7 +33,7 @@ dependencies:
33
33
  version: 4.2.0
34
34
  - - "<"
35
35
  - !ruby/object:Gem::Version
36
- version: '6'
36
+ version: '7'
37
37
  type: :runtime
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
@@ -43,37 +43,47 @@ dependencies:
43
43
  version: 4.2.0
44
44
  - - "<"
45
45
  - !ruby/object:Gem::Version
46
- version: '6'
46
+ version: '7'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rdf
49
49
  requirement: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '2.2'
53
+ version: '3.0'
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: '2.2'
61
- description:
60
+ version: '3.0'
61
+ description:
62
62
  email: arthur@argu.co
63
63
  executables: []
64
64
  extensions: []
65
65
  extra_rdoc_files: []
66
66
  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
67
  - lib/rdf/serializers.rb
68
+ - lib/rdf/serializers/config.rb
69
+ - lib/rdf/serializers/data_type_helper.rb
70
+ - lib/rdf/serializers/hdnjson_parser.rb
71
+ - lib/rdf/serializers/hextuple_serializer.rb
72
+ - lib/rdf/serializers/list_serializer.rb
73
+ - lib/rdf/serializers/lookup_chain.rb
74
+ - lib/rdf/serializers/nil_serializer.rb
75
+ - lib/rdf/serializers/object_serializer.rb
76
+ - lib/rdf/serializers/relationship.rb
71
77
  - lib/rdf/serializers/renderers.rb
78
+ - lib/rdf/serializers/scalar.rb
79
+ - lib/rdf/serializers/serialization_core.rb
80
+ - lib/rdf/serializers/statements.rb
72
81
  - lib/rdf/serializers/version.rb
73
- homepage: https://github.com/argu-co/rdf-serializers
74
- licenses: []
82
+ homepage: https://github.com/ontola/rdf-serializers
83
+ licenses:
84
+ - MIT
75
85
  metadata: {}
76
- post_install_message:
86
+ post_install_message:
77
87
  rdoc_options: []
78
88
  require_paths:
79
89
  - lib
@@ -88,9 +98,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
88
98
  - !ruby/object:Gem::Version
89
99
  version: '0'
90
100
  requirements: []
91
- rubyforge_project:
92
- rubygems_version: 2.6.11
93
- signing_key:
101
+ rubygems_version: 3.1.2
102
+ signing_key:
94
103
  specification_version: 4
95
104
  summary: Adds RDF serialization, like n-triples or turtle, to active model serializers
96
105
  test_files: []
@@ -1,60 +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 subject.blank? || predicate.blank? || data.empty?
9
- data.map do |object|
10
- raise "#{object} is not a RDF::Resource but a #{object.class}" unless object.is_a?(::RDF::Resource)
11
- ::RDF::Statement.new(subject, predicate, object)
12
- end
13
- end
14
-
15
- private
16
-
17
- def data
18
- @data ||=
19
- if association.collection?
20
- objects_for_many(association).compact
21
- else
22
- [object_for_one(association)].compact
23
- end
24
- end
25
-
26
- def objects_for_many(association)
27
- collection_serializer = association.lazy_association.serializer
28
- if collection_serializer.respond_to?(:each)
29
- collection_serializer.map do |serializer|
30
- serializer.read_attribute_for_serialization(:rdf_subject)
31
- end
32
- else
33
- []
34
- end
35
- end
36
-
37
- def object_for_one(association)
38
- if belongs_to_id_on_self?(association)
39
- parent_serializer.read_attribute_for_serialization(:rdf_subject)
40
- else
41
- serializer = association.lazy_association.serializer
42
- if (virtual_value = association.virtual_value)
43
- virtual_value[:id]
44
- elsif serializer && association.object
45
- serializer.read_attribute_for_serialization(:rdf_subject)
46
- end
47
- end
48
- end
49
-
50
- def predicate
51
- @predicate ||= association.reflection.options[:predicate]
52
- end
53
-
54
- def subject
55
- @subject ||= parent_serializer.read_attribute_for_serialization(:rdf_subject)
56
- end
57
- end
58
- end
59
- end
60
- end
@@ -1,125 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveModelSerializers
4
- module Adapter
5
- class RDF < Base
6
- extend ActiveSupport::Autoload
7
- autoload :Relationship
8
-
9
- delegate :object, to: :serializer
10
- delegate :dump, :triples, to: :graph
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
- protected
20
-
21
- attr_reader :fieldset
22
-
23
- private
24
-
25
- def add_attribute(data, subject, value)
26
- predicate = data.options[:predicate]
27
- return unless predicate
28
- value = value.respond_to?(:each) ? value : [value]
29
- value.compact.map { |v| add_triple(subject, predicate, v) }
30
- end
31
-
32
- def add_triple(subject, predicate, object)
33
- obj =
34
- case object
35
- when ::RDF::Term
36
- object
37
- when ActiveSupport::TimeWithZone
38
- ::RDF::Literal(object.to_datetime)
39
- else
40
- ::RDF::Literal(object)
41
- end
42
- @graph << ::RDF::Statement.new(subject, ::RDF::URI(predicate), obj)
43
- end
44
-
45
- def attributes_for(serializer, fields)
46
- serializer.class._attributes_data.map do |key, data|
47
- next if data.excluded?(serializer)
48
- next unless fields.nil? || fields.include?(key)
49
- add_attribute(data, serializer.read_attribute_for_serialization(:rdf_subject), serializer.attributes[key])
50
- end
51
- end
52
-
53
- def custom_triples_for(serializer)
54
- serializer.class.try(:_triples)&.map do |key|
55
- serializer.read_attribute_for_serialization(key).each do |triple|
56
- @graph << triple
57
- end
58
- end
59
- end
60
-
61
- def graph
62
- return @graph if @graph.present?
63
- @graph = ::RDF::Graph.new
64
-
65
- serializers.each { |serializer| process_resource(serializer, @include_directive) }
66
- serializers.each { |serializer| process_relationships(serializer, @include_directive) }
67
- instance_options[:meta]&.each { |meta| add_triple(*meta) }
68
-
69
- @graph
70
- end
71
-
72
- def process_relationship(serializer, include_slice)
73
- return serializer.each { |s| process_relationship(s, include_slice) } if serializer.respond_to?(:each)
74
- return unless serializer&.object && process_resource(serializer, include_slice)
75
- process_relationships(serializer, include_slice)
76
- end
77
-
78
- def process_relationships(serializer, include_slice)
79
- return unless serializer.respond_to?(:associations)
80
- serializer.associations(include_slice).each do |association|
81
- process_relationship(association.lazy_association.serializer, include_slice[association.key])
82
- end
83
- end
84
-
85
- def process_resource(serializer, include_slice = {})
86
- if serializer.is_a?(ActiveModel::Serializer::CollectionSerializer)
87
- return serializer.map { |child| process_resource(child, include_slice) }
88
- end
89
- return unless serializer.respond_to?(:rdf_subject) || serializer.object.respond_to?(:rdf_subject)
90
- return false unless @resource_identifiers.add?(serializer.read_attribute_for_serialization(:rdf_subject))
91
- resource_object_for(serializer, include_slice)
92
- true
93
- end
94
-
95
- def relationships_for(serializer, requested_associations, include_slice)
96
- include_directive = JSONAPI::IncludeDirective.new(requested_associations, allow_wildcard: true)
97
- serializer.associations(include_directive, include_slice).each do |association|
98
- Relationship.new(serializer, instance_options, association).triples.each do |triple|
99
- @graph << triple
100
- end
101
- end
102
- end
103
-
104
- def resource_object_for(serializer, include_slice = {})
105
- type = type_for(serializer, instance_options).to_s
106
- serializer.fetch(self) do
107
- break nil if serializer.read_attribute_for_serialization(:rdf_subject).nil?
108
- requested_fields = fieldset&.fields_for(type)
109
- attributes_for(serializer, requested_fields)
110
- custom_triples_for(serializer)
111
- end
112
- requested_associations = fieldset.fields_for(type) || '*'
113
- relationships_for(serializer, requested_associations, include_slice)
114
- end
115
-
116
- def serializers
117
- serializer.respond_to?(:each) ? serializer : [serializer]
118
- end
119
-
120
- def type_for(serializer, instance_options)
121
- JsonApi::ResourceIdentifier.new(serializer, instance_options).as_json[:type]
122
- end
123
- end
124
- end
125
- end
@@ -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