emp_json 1.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9c980a0bd41e0236610d10af6521e6e41ed39329973c51acdaf48a5567e6162a
4
+ data.tar.gz: a260ad775292dfbdc03c25a2d9472164687b918d73f85af4706a1cad7e31f367
5
+ SHA512:
6
+ metadata.gz: 2b7c38569fc8700ea138d1fb0fc6dfb82caee3b8c3e805f50d5c06a7912f1bba3233c568980b4614d9d8ac4477ced6fb07dfdb3482fa00ec4eafbb69722500af
7
+ data.tar.gz: 591b643b63e2d0e497c7ba8c0989518a357b34d0b3d36b04b72872d9c11637bf5a28215aeb23f779ba5066c939bd72ad680d7e68d3d00c1b70d0dc27fb5c92e3
data/.rubocop.yml ADDED
@@ -0,0 +1,24 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.7
3
+ NewCops: enable
4
+
5
+
6
+ Style/StringLiterals:
7
+ Enabled: true
8
+ EnforcedStyle: double_quotes
9
+
10
+ Style/StringLiteralsInInterpolation:
11
+ Enabled: true
12
+ EnforcedStyle: double_quotes
13
+
14
+ Metrics/PerceivedComplexity:
15
+ Max: 10
16
+
17
+ Metrics/CyclomaticComplexity:
18
+ Max: 10
19
+
20
+ Metrics/MethodLength:
21
+ Max: 20
22
+
23
+ Layout/LineLength:
24
+ Max: 120
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2022-05-09
4
+
5
+ - Initial release
@@ -0,0 +1 @@
1
+ Be civil
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in emp_json_rb.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "minitest", "~> 5.0"
11
+
12
+ gem "rubocop", "~> 1.21"
13
+
14
+ gem "activemodel", "~> 7"
15
+
16
+ gem "activesupport", "~> 7"
17
+
18
+ gem "rdf"
data/Gemfile.lock ADDED
@@ -0,0 +1,62 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ emp_json (1.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ activemodel (7.0.3)
10
+ activesupport (= 7.0.3)
11
+ activesupport (7.0.3)
12
+ concurrent-ruby (~> 1.0, >= 1.0.2)
13
+ i18n (>= 1.6, < 2)
14
+ minitest (>= 5.1)
15
+ tzinfo (~> 2.0)
16
+ ast (2.4.2)
17
+ concurrent-ruby (1.1.10)
18
+ i18n (1.10.0)
19
+ concurrent-ruby (~> 1.0)
20
+ link_header (0.0.8)
21
+ minitest (5.15.0)
22
+ parallel (1.22.1)
23
+ parser (3.1.2.0)
24
+ ast (~> 2.4.1)
25
+ rainbow (3.1.1)
26
+ rake (13.0.6)
27
+ rdf (3.2.7)
28
+ link_header (~> 0.0, >= 0.0.8)
29
+ regexp_parser (2.3.1)
30
+ rexml (3.2.5)
31
+ rubocop (1.29.0)
32
+ parallel (~> 1.10)
33
+ parser (>= 3.1.0.0)
34
+ rainbow (>= 2.2.2, < 4.0)
35
+ regexp_parser (>= 1.8, < 3.0)
36
+ rexml (>= 3.2.5, < 4.0)
37
+ rubocop-ast (>= 1.17.0, < 2.0)
38
+ ruby-progressbar (~> 1.7)
39
+ unicode-display_width (>= 1.4.0, < 3.0)
40
+ rubocop-ast (1.17.0)
41
+ parser (>= 3.1.1.0)
42
+ ruby-progressbar (1.11.0)
43
+ tzinfo (2.0.4)
44
+ concurrent-ruby (~> 1.0)
45
+ unicode-display_width (2.1.0)
46
+
47
+ PLATFORMS
48
+ arm64-darwin-21
49
+ ruby
50
+ x86_64-linux
51
+
52
+ DEPENDENCIES
53
+ activemodel (~> 7)
54
+ activesupport (~> 7)
55
+ emp_json!
56
+ minitest (~> 5.0)
57
+ rake (~> 13.0)
58
+ rdf
59
+ rubocop (~> 1.21)
60
+
61
+ BUNDLED WITH
62
+ 2.3.9
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Thom van Kalkeren
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # Empathy::Json
2
+
3
+ A gem to serialize and manipulate [EmpJson](https://empathy.tools/specifications/emp-json) slices.
4
+
5
+ ## Installation
6
+
7
+ Install the gem and add to the application's Gemfile by executing:
8
+
9
+ $ bundle add emp_json_rb
10
+
11
+ If bundler is not being used to manage dependencies, install the gem by executing:
12
+
13
+ $ gem install emp_json_rb
14
+
15
+ ## Usage
16
+
17
+ The module was built to work with [rdf-serializers](https://github.com/ontola/rdf-serializers), and uses serializer descriptions from that gem.
18
+
19
+ Include the `Empathy::Json::Slices` module and call `emp_json_hash(resource)` for a hash representation or `render_emp_json(resource)` for a stringified version.
20
+
21
+ ## Development
22
+
23
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
24
+
25
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
26
+
27
+ ## Contributing
28
+
29
+ Bug reports and pull requests are welcome on GitHub at https://github.com/empathy-tools/emp_json_rb. Contributors are expected to adhere to the [code of conduct](https://github.com/empathy-tools/emp_json_rb/blob/main/CODE_OF_CONDUCT.md).
30
+
31
+ ## License
32
+
33
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*.rb"]
10
+ end
11
+
12
+ require "rubocop/rake_task"
13
+
14
+ RuboCop::RakeTask.new
15
+
16
+ task default: %i[test rubocop]
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/empathy/emp_json/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "emp_json"
7
+ spec.version = Empathy::EmpJson::VERSION
8
+ spec.authors = ["Thom van Kalkeren"]
9
+ spec.email = ["thom@ontola.io"]
10
+
11
+ spec.summary = "Serializer for the EmpJson specification."
12
+ spec.description = "Serializer for the EmpJson specification."
13
+ spec.homepage = "https://empathy.tools/tools/emp-json"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.7.0.preview1"
16
+
17
+ # spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://github.com/empathy-tools/emp_json_rb"
21
+ spec.metadata["bug_tracker_uri"] = "https://github.com/empathy-tools/emp_json_rb/issues"
22
+ spec.metadata["changelog_uri"] = "https://github.com/empathy-tools/emp_json_rb/CHANGELOG.md"
23
+
24
+ # Specify which files should be added to the gem when it is released.
25
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
26
+ spec.files = Dir.chdir(__dir__) do
27
+ `git ls-files -z`.split("\x0").reject do |f|
28
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
29
+ end
30
+ end
31
+ spec.bindir = "exe"
32
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
33
+ spec.require_paths = ["lib"]
34
+
35
+ # Uncomment to register a new dependency of your gem
36
+ # spec.add_dependency "example-gem", "~> 1.0"
37
+
38
+ # For more information and examples about making a new gem, check out our
39
+ # guide at: https://bundler.io/guides/creating_gem.html
40
+ spec.metadata["rubygems_mfa_required"] = "true"
41
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/time_with_zone"
4
+
5
+ module Empathy
6
+ module EmpJson
7
+ module Helpers
8
+ # Functions relating to serializing primitives.
9
+ module Primitives # rubocop:disable Metrics/ModuleLength
10
+ EMP_TYPE_GLOBAL_ID = "id"
11
+ EMP_TYPE_LOCAL_ID = "lid"
12
+ EMP_TYPE_DATETIME = "dt"
13
+ EMP_TYPE_STRING = "s"
14
+ EMP_TYPE_BOOL = "b"
15
+ EMP_TYPE_INTEGER = "i"
16
+ EMP_TYPE_LONG = "l"
17
+ EMP_TYPE_PRIMITIVE = "p"
18
+ EMP_TYPE_LANGSTRING = "ls"
19
+
20
+ RDF_RDFV = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
21
+ RDF_RDFV_LANGSTRING = "#{RDF_RDFV}langString"
22
+ RDF_RDFS = "http://www.w3.org/2000/01/rdf-schema#"
23
+ RDF_XSD = "http://www.w3.org/2001/XMLSchema#"
24
+ RDF_XSD_TOKEN = "#{RDF_XSD}token"
25
+ RDF_XSD_STRING = "#{RDF_XSD}string"
26
+ RDF_XSD_DATETIME = "#{RDF_XSD}dateTime"
27
+ RDF_XSD_BOOLEAN = "#{RDF_XSD}boolean"
28
+ RDF_XSD_INTEGER = "#{RDF_XSD}integer"
29
+
30
+ def object_to_value(value)
31
+ return primitive_to_value(value.iri) if value.respond_to?(:iri)
32
+
33
+ return primitive_to_value(value.subject) if value.is_a?(RDF::List)
34
+
35
+ primitive_to_value(value)
36
+ end
37
+
38
+ def node_to_local_id(value)
39
+ shorthand(EMP_TYPE_LOCAL_ID, "_:#{value.id}")
40
+ end
41
+
42
+ def primitive_to_value(value) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize
43
+ throw_unknown_ruby_object(value) if value.nil?
44
+
45
+ case value
46
+ when RDF::Node
47
+ node_to_local_id(value)
48
+ when RDF::URI, URI
49
+ shorthand(EMP_TYPE_GLOBAL_ID, value.to_s)
50
+ when DateTime, ActiveSupport::TimeWithZone
51
+ shorthand(EMP_TYPE_DATETIME, value.iso8601)
52
+ when String
53
+ shorthand(EMP_TYPE_STRING, value)
54
+ when true, false
55
+ shorthand(EMP_TYPE_BOOL, value.to_s)
56
+ when Symbol
57
+ primitive(RDF_XSD_TOKEN, value.to_s)
58
+ when Integer
59
+ integer_to_value(value)
60
+ when Float, Numeric
61
+ use_rdf_rb_for_primitive(value)
62
+ when RDF::Literal
63
+ rdf_literal_to_value(value)
64
+ else
65
+ throw_unknown_ruby_object(value)
66
+ end
67
+ end
68
+
69
+ def integer_to_value(value)
70
+ size = value.bit_length
71
+ if size <= 32
72
+ shorthand(EMP_TYPE_INTEGER, value.to_s)
73
+ elsif size > 32 && size <= 64
74
+ shorthand(EMP_TYPE_LONG, value.to_s)
75
+ else
76
+ use_rdf_rb_for_primitive(value)
77
+ end
78
+ end
79
+
80
+ def rdf_literal_to_value(value)
81
+ case value.datatype.to_s
82
+ when RDF_RDFV_LANGSTRING
83
+ {
84
+ type: EMP_TYPE_LANGSTRING,
85
+ l: value.language.to_s,
86
+ v: value.value
87
+ }
88
+ when RDF_XSD_STRING
89
+ shorthand(EMP_TYPE_STRING, value.value)
90
+ when RDF_XSD_DATETIME
91
+ shorthand(EMP_TYPE_DATETIME, value.value)
92
+ when RDF_XSD_BOOLEAN
93
+ shorthand(EMP_TYPE_BOOL, value.value)
94
+ when RDF_XSD_INTEGER
95
+ integer_to_value(value.to_i)
96
+ else
97
+ throw "unknown RDF::Literal"
98
+ end
99
+ end
100
+
101
+ def use_rdf_rb_for_primitive(value)
102
+ rdf = RDF::Literal(value)
103
+ primitive(rdf.datatype.to_s, rdf.value)
104
+ end
105
+
106
+ def shorthand(type, value)
107
+ {
108
+ type: type,
109
+ v: value
110
+ }
111
+ end
112
+
113
+ def primitive(datatype, value)
114
+ {
115
+ type: EMP_TYPE_PRIMITIVE,
116
+ dt: datatype,
117
+ v: value
118
+ }
119
+ end
120
+
121
+ def throw_unknown_ruby_object(value)
122
+ throw "unknown ruby object: #{value} (#{value.class})"
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Empathy
4
+ module EmpJson
5
+ module Helpers
6
+ # Functions relating to serializing RDF::List records
7
+ module RDFList
8
+ def add_rdf_list_to_slice(slice, symbolize, **options)
9
+ elem = options.delete(:resource)
10
+ loop do
11
+ list_item_to_record(slice, elem, symbolize)
12
+
13
+ break if elem.rest_subject == NS.rdfv.nil
14
+
15
+ elem = elem.rest
16
+ end
17
+ end
18
+
19
+ def list_item_to_record(slice, elem, symbolize) # rubocop:disable Metrics/AbcSize
20
+ rid = add_record_to_slice(slice, elem)
21
+ add_attribute_to_record(slice, rid, NS.rdfv.type, NS.rdfv.List, symbolize)
22
+ first = elem.first.is_a?(RDF::Term) ? elem.first : retrieve_id(elem.first)
23
+ add_attribute_to_record(slice, rid, NS.rdfv.first, first, symbolize)
24
+ add_attribute_to_record(slice, rid, NS.rdfv.rest, elem.rest_subject, symbolize)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/indifferent_access"
4
+
5
+ module Empathy
6
+ module EmpJson
7
+ module Helpers
8
+ # Additional functions for working with slices
9
+ module Slices
10
+ # Retrieves all values for the given [field].
11
+ def all_values_from_slice(slice, field, _website_iri = nil)
12
+ slice
13
+ .values
14
+ .flat_map { |record| field_from_record(record, field) }
15
+ .map { |values| normalise_slice_values(values) }
16
+ .compact
17
+ end
18
+
19
+ def add_record_to_slice(slice, resource)
20
+ id = resource.is_a?(RDF::Resource) ? resource : retrieve_id(resource)
21
+ value = primitive_to_value(id)
22
+
23
+ unless slice_includes_record?(slice, resource)
24
+ slice[value[:v]] = {
25
+ _id: value
26
+ }
27
+ end
28
+
29
+ value[:v]
30
+ end
31
+
32
+ # Returns a normalised fields array for a record from a slice.
33
+ def values_from_slice(slice, id, field, website_iri = nil)
34
+ values = field_from_slice(slice, id, field, website_iri)
35
+
36
+ normalise_slice_values(values)
37
+ end
38
+
39
+ # Returns the fields for a record from a slice.
40
+ def field_from_slice(slice, id, field, website_iri = nil)
41
+ record = record_from_slice(slice, id, website_iri)
42
+ return unless record.present?
43
+
44
+ field_from_record(record, field)
45
+ end
46
+
47
+ # Returns a record from a slice.
48
+ def record_from_slice(slice, id, website_iri = nil)
49
+ slice[absolutized_id(retrieve_id(id), website_iri)] || slice[retrieve_id(id)]
50
+ end
51
+
52
+ def field_from_record(record, field)
53
+ field = URI(field.to_s)
54
+ symbolized = (field.fragment || field.path.split("/").last).camelize(:lower)
55
+ (record[field.to_s] || record[symbolized])&.compact
56
+ end
57
+
58
+ def retrieve_id(resource)
59
+ return resource.to_s if resource.is_a?(URI) || resource.is_a?(RDF::URI)
60
+
61
+ resource.try(:iri) || resource.try(:subject) || resource.try(:id) || resource
62
+ end
63
+
64
+ def absolutized_id(id, website_iri = nil)
65
+ return id.to_s.delete_prefix(website_iri) if website_iri.present?
66
+
67
+ id.to_s
68
+ end
69
+
70
+ def normalise_slice_values(values)
71
+ values.is_a?(Array) ? values.map(&:with_indifferent_access) : values&.with_indifferent_access
72
+ end
73
+
74
+ def expand(slice, website_iri = current_tenant)
75
+ return slice unless website_iri.present?
76
+
77
+ slice.transform_keys { |k| k.start_with?("/") ? website_iri + k : k }
78
+ end
79
+
80
+ def field_to_symbol(uri)
81
+ (uri.fragment || uri.path.split("/").last).camelize(:lower)
82
+ end
83
+
84
+ def slice_includes_record?(slice, resource)
85
+ slice.key?(object_to_value(resource)[:v])
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Empathy
4
+ module EmpJson
5
+ module Helpers
6
+ # Functions relating to adding values to records
7
+ module Values
8
+ def add_attribute_to_record(slice, rid, key, value, symbolize)
9
+ return if value.nil?
10
+
11
+ symbol = uri_to_symbol(key, symbolize)
12
+ emp_value = value_to_emp_value(value)
13
+ current_value = slice[rid][symbol]
14
+
15
+ slice[rid][symbol] =
16
+ if current_value.is_a?(Array)
17
+ [*current_value, *(emp_value.is_a?(Array) ? emp_value : [emp_value])]
18
+ elsif current_value
19
+ [current_value, *(emp_value.is_a?(Array) ? emp_value : [emp_value])]
20
+ else
21
+ emp_value
22
+ end
23
+ end
24
+
25
+ def wrap_relation_in_sequence(value, resource)
26
+ LinkedRails::Sequence.new(
27
+ value.is_a?(Array) ? value : [value],
28
+ parent: resource,
29
+ scope: false
30
+ )
31
+ end
32
+
33
+ def uri_to_symbol(uri, symbolize)
34
+ return uri.to_s unless symbolize
35
+
36
+ casing = symbolize == :class ? :upper : :lower
37
+
38
+ (uri.fragment || uri.path.split("/").last).camelize(casing)
39
+ end
40
+
41
+ def value_to_emp_value(value)
42
+ return object_to_value(value) if value.nil?
43
+
44
+ case value
45
+ when SUPPORTS_SEQUENCE ? LinkedRails::Sequence : nil
46
+ object_to_value(value.iri)
47
+ when SUPPORTS_RDF_RB ? RDF::List : nil
48
+ object_to_value(value.subject)
49
+ when Array, *(SUPPORTS_AR ? [ActiveRecord::Associations::CollectionProxy, ActiveRecord::Relation] : [])
50
+ value.map { |v| object_to_value(v) }.compact
51
+ else
52
+ object_to_value(value)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Empathy
4
+ module EmpJson
5
+ module Serializer
6
+ # Functions relating to serializing attributes, relations and statements
7
+ module Fields
8
+ # Modifies the record parameter
9
+ def serialize_attributes_to_record(serializer, slice, resource, rid, serialization_params)
10
+ return [] if serializer.attributes_to_serialize.blank?
11
+
12
+ nested_resources = []
13
+ serializer.attributes_to_serialize.each do |_, attr|
14
+ next if attr.predicate.blank? || !attr.conditionally_allowed?(resource, serialization_params)
15
+
16
+ value = value_for_attribute(attr, resource, serialization_params)
17
+ symbol = predicate_to_symbol(attr)
18
+ add_attribute_to_record(slice, rid, symbol, value, false)
19
+ collect_nested_resources(nested_resources, slice, resource, value, serialization_params) if value.present?
20
+ end
21
+
22
+ nested_resources
23
+ end
24
+
25
+ # Modifies the slice parameter
26
+ def serialize_statements(serializer, slice, resource, serialization_params)
27
+ serializer._statements&.each do |key|
28
+ serializer.send(key, resource, serialization_params).each do |statement|
29
+ subject, predicate, value = unpack_statement(statement)
30
+
31
+ next if value.nil?
32
+
33
+ symbol = uri_to_symbol(predicate, symbolize)
34
+ rid = add_record_to_slice(slice, subject)
35
+ add_attribute_to_record(slice, rid, symbol, value, false)
36
+ end
37
+ end
38
+ end
39
+
40
+ # Modifies the record parameter
41
+ def serialize_relations_to_record(serializer, slice, resource, rid, serialization_params)
42
+ return [] if serializer.relationships_to_serialize.blank?
43
+
44
+ nested_resources = []
45
+ serializer.relationships_to_serialize.each do |_, relationship|
46
+ next unless relationship.include_relationship?(resource, serialization_params)
47
+
48
+ value = value_for_relation(relationship, resource, serialization_params)
49
+ next if value.nil?
50
+
51
+ symbol = predicate_to_symbol(relationship)
52
+ add_attribute_to_record(slice, rid, symbol, value, false)
53
+
54
+ collect_nested_resources(nested_resources, slice, resource, value, serialization_params)
55
+ end
56
+
57
+ nested_resources
58
+ end
59
+
60
+ def value_for_attribute(attr, resource, serialization_params)
61
+ return resource.try(attr.method) if attr.method.is_a?(Symbol)
62
+
63
+ FastJsonapi.call_proc(attr.method, resource, serialization_params)
64
+ end
65
+
66
+ def value_for_relation(relation, resource, serialization_params)
67
+ value = unpack_relation_value(relation, resource, serialization_params)
68
+
69
+ return if value.nil?
70
+
71
+ if SUPPORTS_SEQUENCE && relation.sequence
72
+ wrap_relation_in_sequence(value, resource)
73
+ else
74
+ value
75
+ end
76
+ end
77
+
78
+ def unpack_relation_value(relation, resource, serialization_params)
79
+ if relation.object_block
80
+ FastJsonapi.call_proc(relation.object_block, resource, serialization_params)
81
+ else
82
+ resource.try(relation.key)
83
+ end
84
+ end
85
+
86
+ def unpack_statement(statement)
87
+ subject = statement.try(:subject) || statement[0]
88
+ predicate = statement.try(:predicate) || statement[1]
89
+ value = statement.try(:object) || statement[2]
90
+
91
+ [subject, predicate, value]
92
+ end
93
+
94
+ def predicate_to_symbol(attr)
95
+ return uri_to_symbol(attr.predicate, symbolize) if attr.predicate.present?
96
+
97
+ attr.key
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Empathy
4
+ module EmpJson
5
+ module Serializer
6
+ # Functions relating to determining inclusion of records during serialization.
7
+ module Inclusion
8
+ # Modifies the nested_resources parameter
9
+ def collect_nested_resources(nested_resources, slice, resource, value, serialization_params)
10
+ case value
11
+ when LinkedRails::Sequence
12
+ collect_sequence_and_members(nested_resources, slice, resource, value, serialization_params)
13
+ when RDF::List
14
+ nested_resources.push value unless value.subject == NS.rdfv.nil
15
+ when Array
16
+ collect_array_members(nested_resources, slice, resource, value, serialization_params)
17
+ else
18
+ nested_resources.push value if blank_value?(value) || nested_resource?(resource, value)
19
+ end
20
+ end
21
+
22
+ def collect_sequence_and_members(nested_resources, slice, resource, value, serialization_params)
23
+ value.members.map { |m| collect_nested_resources(nested_resources, slice, resource, m, serialization_params) }
24
+ add_sequence_to_slice(slice, serialization_params, resource: value) unless slice[value.iri.to_s]
25
+ end
26
+
27
+ def collect_array_members(nested_resources, slice, resource, value, serialization_params)
28
+ value.each { |m| collect_nested_resources(nested_resources, slice, resource, m, serialization_params) }
29
+ end
30
+
31
+ def blank_value?(value)
32
+ value.try(:iri)&.is_a?(RDF::Node)
33
+ end
34
+
35
+ def nested_resource?(resource, value)
36
+ value.try(:iri)&.to_s&.include?("#") &&
37
+ !resource.iri.to_s.include?("#") &&
38
+ value.iri.to_s.starts_with?(resource.iri.to_s)
39
+ end
40
+
41
+ def process_includes(slice, serialization_params, **options)
42
+ includes = options.delete(:includes)
43
+ resource = options.delete(:resource)
44
+
45
+ includes.each do |prop, nested|
46
+ value = resource.try(prop)
47
+ next if value.blank?
48
+
49
+ (value.is_a?(Array) || value.is_a?(ActiveRecord::Relation) ? value : [value]).each do |v|
50
+ next if slice_includes_record?(slice, v)
51
+
52
+ resource_to_emp_json(slice, serialization_params, resource: v, includes: nested, **options)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support"
4
+ require "active_support/core_ext/object/try"
5
+
6
+ module Empathy
7
+ module EmpJson
8
+ module Serializer
9
+ # Functions relating to serializing records
10
+ module Records
11
+ def serialize_record(slice, serialization_params, **options)
12
+ resource = options.delete(:resource)
13
+ serializer = options.delete(:serializer_class) || RDF::Serializers.serializer_for(resource)
14
+
15
+ rid = add_record_to_slice(slice, resource)
16
+
17
+ nested = serialize_record_fields(serializer, slice, resource, rid, serialization_params)
18
+
19
+ nested
20
+ .reject { |r| slice_includes_record?(slice, r) }
21
+ .each { |r| resource_to_emp_json(slice, serialization_params, resource: r) }
22
+ process_includes(slice, serialization_params, resource: resource, **options) if options[:includes]
23
+ end
24
+
25
+ def serialize_record_fields(serializer, slice, resource, rid, serialization_params)
26
+ nested = serialize_attributes_to_record(serializer, slice, resource, rid, serialization_params)
27
+ nested += serialize_relations_to_record(serializer, slice, resource, rid, serialization_params)
28
+
29
+ serialize_statements(serializer, slice, resource, serialization_params)
30
+
31
+ nested
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Empathy
4
+ module EmpJson
5
+ module Serializer
6
+ # Functions relating to serializing RDF::Sequence records
7
+ module Sequence
8
+ def add_sequence_to_slice(slice, serialization_params, **options)
9
+ resource = options.delete(:resource)
10
+ serializer = options[:serializer_class] || RDF::Serializers.serializer_for(resource)
11
+
12
+ rid = add_record_to_slice(slice, resource)
13
+
14
+ serialize_attributes_to_record(serializer, slice, resource, rid, serialization_params)
15
+ process_sequence_members(serializer, slice, resource, rid, serialization_params, **options)
16
+ end
17
+
18
+ def process_sequence_members(serializer, slice, resource, rid, serialization_params, **options) # rubocop:disable Metrics/ParameterLists
19
+ nested = []
20
+ resource.members.each_with_index.map do |m, i|
21
+ process_sequence_member(m, i, nested, serializer, slice, resource, rid, serialization_params)
22
+ end
23
+ nested.each do |r|
24
+ resource_to_emp_json(slice, serialization_params, resource: r, includes: options[:includes])
25
+ end
26
+ end
27
+
28
+ def process_sequence_member(member, index, nested, serializer, slice, resource, rid, serialization_params) # rubocop:disable Metrics/ParameterLists
29
+ index_predicate = serializer.relationships_to_serialize[:members].predicate
30
+ symbol = uri_to_symbol(index_predicate.call(self, index), symbolize)
31
+ slice[rid][symbol] = value_to_emp_value(member)
32
+ collect_nested_resources(nested, slice, resource, member, serialization_params)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Empathy
4
+ module EmpJson
5
+ module Serializer
6
+ # Module for serializing records to slices.
7
+ module Serializing
8
+ def render_emp_json(resource = @resource)
9
+ Oj.fast_generate(emp_json_hash(resource))
10
+ end
11
+
12
+ def emp_json_hash(_resource = @resource)
13
+ create_slice(@resource)
14
+ end
15
+
16
+ def create_slice(resources)
17
+ slice = {}
18
+
19
+ (resources.is_a?(Array) ? resources : [resources]).each do |resource|
20
+ resource_to_emp_json(
21
+ slice,
22
+ @params,
23
+ resource: resource,
24
+ serializer_class: self.class,
25
+ includes: @rdf_includes
26
+ )
27
+ end
28
+
29
+ slice
30
+ end
31
+
32
+ def resource_to_emp_json(slice, serialization_params, **options) # rubocop:disable Metrics/AbcSize
33
+ return if options[:resource].blank? || retrieve_id(options[:resource]).is_a?(Proc)
34
+
35
+ if SUPPORTS_SEQUENCE && options[:resource].is_a?(LinkedRails::Sequence)
36
+ add_sequence_to_slice(
37
+ slice,
38
+ serialization_params,
39
+ **options
40
+ )
41
+ elsif SUPPORTS_RDF_RB && options[:resource].is_a?(RDF::List)
42
+ add_rdf_list_to_slice(slice, symbolize, **options)
43
+ else
44
+ if options[:serializer_class] == RDF::Serializers::ListSerializer
45
+ raise("Trying to serialize mixed resources")
46
+ end
47
+
48
+ serialize_record(
49
+ slice,
50
+ serialization_params,
51
+ **options
52
+ )
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "serializer/fields"
4
+ require_relative "serializer/inclusion"
5
+ require_relative "serializer/records"
6
+ require_relative "serializer/sequence"
7
+ require_relative "serializer/serializing"
8
+
9
+ module Empathy
10
+ module EmpJson
11
+ # Module to use with the rdf-serializers gem.
12
+ module Serializer
13
+ extend ActiveSupport::Concern
14
+
15
+ include Helpers::Primitives
16
+ include Helpers::RDFList
17
+ include Helpers::Slices
18
+ include Helpers::Values
19
+ include Fields
20
+ include Inclusion
21
+ include Records
22
+ include Sequence
23
+ include Serializing
24
+
25
+ included do
26
+ attr_accessor :symbolize
27
+ end
28
+
29
+ def initialize(resource, opts = {})
30
+ self.symbolize = opts.delete(:symbolize)
31
+
32
+ super
33
+ end
34
+
35
+ def dump(*args, **options)
36
+ case args.first
37
+ when :empjson
38
+ render_emp_json
39
+ else
40
+ super(*args, **options)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Empathy
4
+ module EmpJson
5
+ VERSION = "1.1.0"
6
+ end
7
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "empathy/emp_json/helpers/primitives"
4
+ require "empathy/emp_json/helpers/rdf_list"
5
+ require "empathy/emp_json/helpers/slices"
6
+ require "empathy/emp_json/helpers/values"
7
+ require "empathy/emp_json/serializer"
8
+
9
+ module Empathy
10
+ module EmpJson
11
+ SUPPORTS_RDF_RB = Object.const_defined?("RDF")
12
+ SUPPORTS_SEQUENCE = Object.const_defined?("LinkedRails")
13
+ SUPPORTS_AR = Object.const_defined?("ActiveRecord")
14
+
15
+ class Error < StandardError; end
16
+ end
17
+ end
@@ -0,0 +1,8 @@
1
+ module Emp
2
+ module Json
3
+ module Rb
4
+ VERSION: String
5
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
6
+ end
7
+ end
8
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: emp_json
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Thom van Kalkeren
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-05-31 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Serializer for the EmpJson specification.
14
+ email:
15
+ - thom@ontola.io
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".rubocop.yml"
21
+ - CHANGELOG.md
22
+ - CODE_OF_CONDUCT.md
23
+ - Gemfile
24
+ - Gemfile.lock
25
+ - LICENSE.txt
26
+ - README.md
27
+ - Rakefile
28
+ - emp-json-rb.gemspec
29
+ - lib/empathy/emp_json.rb
30
+ - lib/empathy/emp_json/helpers/primitives.rb
31
+ - lib/empathy/emp_json/helpers/rdf_list.rb
32
+ - lib/empathy/emp_json/helpers/slices.rb
33
+ - lib/empathy/emp_json/helpers/values.rb
34
+ - lib/empathy/emp_json/serializer.rb
35
+ - lib/empathy/emp_json/serializer/fields.rb
36
+ - lib/empathy/emp_json/serializer/inclusion.rb
37
+ - lib/empathy/emp_json/serializer/records.rb
38
+ - lib/empathy/emp_json/serializer/sequence.rb
39
+ - lib/empathy/emp_json/serializer/serializing.rb
40
+ - lib/empathy/emp_json/version.rb
41
+ - sig/emp/json/rb.rbs
42
+ homepage: https://empathy.tools/tools/emp-json
43
+ licenses:
44
+ - MIT
45
+ metadata:
46
+ homepage_uri: https://empathy.tools/tools/emp-json
47
+ source_code_uri: https://github.com/empathy-tools/emp_json_rb
48
+ bug_tracker_uri: https://github.com/empathy-tools/emp_json_rb/issues
49
+ changelog_uri: https://github.com/empathy-tools/emp_json_rb/CHANGELOG.md
50
+ rubygems_mfa_required: 'true'
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 2.7.0.preview1
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubygems_version: 3.3.7
67
+ signing_key:
68
+ specification_version: 4
69
+ summary: Serializer for the EmpJson specification.
70
+ test_files: []