graphiti-activegraph 1.3.3 → 1.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a536e8f4ef99e0afb0a76ab5725a367f0642d0825a58ed7d08afdb552a06fe7
4
- data.tar.gz: 0e6835102537951df5d70f5426339ba7fe8fa83e0be9bea3a310c9f908de9bcb
3
+ metadata.gz: e31914fe6d6f7f7ab7a391ede7de332f9e3dfb6ee633f8efc7c601b9ae18ad4e
4
+ data.tar.gz: 65cbf76ded94827c57e8ce27ab009001e53883d33e0f75feafd9c6b887da6087
5
5
  SHA512:
6
- metadata.gz: 3e58d8b0097c4299f5f418e4ebca415ead2ca75f962dc1dd52e3ed839375de64f6b83beed9accfcd31fb8d9b5f6d22d336ae112d93b4f527c16a41d8b3be31ce
7
- data.tar.gz: e0f56767adc0a4922ac7fcc2a9c4a1b2fab7efc3bba8f248b18422e89360501f6bd325a95602e45c862eb0831664f4603fa7472cc2fe1f1c79ef2fa49f100568
6
+ metadata.gz: a7ee5ff8f6d47403706a64c2dc002b2f9cdc7b439f1f4ccd11e904981e489f2e8320e6e3cea029b18ccf89b0bf41b28a865d9c268d062e9cb247c34395b9e79e
7
+ data.tar.gz: fb36ab1994476e560ea25beaa000f16b2159da649a5516919aa46a762c68643a2f97dcb289a2f334093a5f157a4e733a0d85cc8259690bae035d88a870922414
@@ -1,58 +1,58 @@
1
- name: Specs
2
-
3
- on:
4
- push:
5
- branches: [ "master" ]
6
- pull_request:
7
- branches: [ "master" ]
8
-
9
- # Allows you to run this workflow manually from the Actions tab
10
- workflow_dispatch:
11
-
12
- permissions:
13
- actions: write
14
- contents: read
15
-
16
- jobs:
17
- test:
18
- runs-on: ubuntu-latest
19
-
20
- strategy:
21
- fail-fast: false
22
- matrix:
23
- ruby-version: [ '3.4', '4.0.3' ] # Removed 'jruby' till https://github.com/jruby/jruby/issues/8642 is fixed
24
- neo4j: [ '5.26.0', '5.23.0' ]
25
- include:
26
- - ruby: jruby
27
- java-version: 17
28
- env:
29
- JRUBY_OPTS: --debug -J-Xmx1280m -Xcompile.invokedynamic=false -J-XX:+TieredCompilation -J-XX:TieredStopAtLevel=1 -J-noverify -Xcompile.mode=OFF
30
- steps:
31
- - name: Start neo4j
32
- run: docker run --name neo4j --env NEO4J_AUTH=neo4j/password --env NEO4J_ACCEPT_LICENSE_AGREEMENT=yes --env NEO4J_dbms_directories_import= -p7687:7687 -p7474:7474 -v `pwd`/tmp:/var/lib/neo4j/import --rm neo4j:${{ matrix.neo4j }}-enterprise &
33
-
34
- - uses: actions/checkout@v3
35
-
36
- - name: Set up Ruby
37
- uses: ruby/setup-ruby@v1
38
- with:
39
- ruby-version: ${{ matrix.ruby-version }}
40
- bundler-cache: true
41
-
42
- - name: Set up Java
43
- uses: actions/setup-java@v4
44
- if: matrix.java-version
45
- with:
46
- distribution: 'temurin'
47
- java-version: ${{ matrix.java-version }}
48
-
49
- - name: Wait for neo4j
50
- run: while [ $((curl localhost:7474/ > /dev/null 2>&1); echo $?) -ne 0 ]; do sleep 1; done
51
-
52
- - name: Run tests
53
- run: bundle exec rspec
54
-
55
- - name: Upload coverage reports to Codecov
56
- uses: codecov/codecov-action@v5
57
- with:
58
- token: ${{ secrets.CODECOV_TOKEN }}
1
+ name: Specs
2
+
3
+ on:
4
+ push:
5
+ branches: [ "master" ]
6
+ pull_request:
7
+ branches: [ "master" ]
8
+
9
+ # Allows you to run this workflow manually from the Actions tab
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ actions: write
14
+ contents: read
15
+
16
+ jobs:
17
+ test:
18
+ runs-on: ubuntu-latest
19
+
20
+ strategy:
21
+ fail-fast: false
22
+ matrix:
23
+ ruby-version: [ '3.4', '4.0.3' ] # Removed 'jruby' till https://github.com/jruby/jruby/issues/8642 is fixed
24
+ neo4j: [ '5.26.0', '5.23.0' ]
25
+ include:
26
+ - ruby: jruby
27
+ java-version: 17
28
+ env:
29
+ JRUBY_OPTS: --debug -J-Xmx1280m -Xcompile.invokedynamic=false -J-XX:+TieredCompilation -J-XX:TieredStopAtLevel=1 -J-noverify -Xcompile.mode=OFF
30
+ steps:
31
+ - name: Start neo4j
32
+ run: docker run --name neo4j --env NEO4J_AUTH=neo4j/password --env NEO4J_ACCEPT_LICENSE_AGREEMENT=yes --env NEO4J_PLUGINS='["apoc"]' --env NEO4J_dbms_directories_import= -p7687:7687 -p7474:7474 -v `pwd`/tmp:/var/lib/neo4j/import --rm neo4j:${{ matrix.neo4j }}-enterprise &
33
+
34
+ - uses: actions/checkout@v3
35
+
36
+ - name: Set up Ruby
37
+ uses: ruby/setup-ruby@v1
38
+ with:
39
+ ruby-version: ${{ matrix.ruby-version }}
40
+ bundler-cache: true
41
+
42
+ - name: Set up Java
43
+ uses: actions/setup-java@v4
44
+ if: matrix.java-version
45
+ with:
46
+ distribution: 'temurin'
47
+ java-version: ${{ matrix.java-version }}
48
+
49
+ - name: Wait for neo4j
50
+ run: while [ $((curl localhost:7474/ > /dev/null 2>&1); echo $?) -ne 0 ]; do sleep 1; done
51
+
52
+ - name: Run tests
53
+ run: bundle exec rspec
54
+
55
+ - name: Upload coverage reports to Codecov
56
+ uses: codecov/codecov-action@v5
57
+ with:
58
+ token: ${{ secrets.CODECOV_TOKEN }}
data/.hound.yml CHANGED
@@ -1,4 +1,7 @@
1
- rubocop:
2
- enabled: true
3
-
4
- fail_on_violations: true
1
+ rubocop:
2
+ enabled: true
3
+ config_file: .rubocop.yml
4
+ version: 1.22.1
5
+
6
+ fail_on_violations: true
7
+
data/.rubocop.yml CHANGED
@@ -1,6 +1,48 @@
1
- require: standard
2
- inherit_gem:
3
- standard: config/base.yml
4
-
5
- Layout/LineLength:
6
- Max: 120
1
+ AllCops:
2
+ TargetRubyVersion: 3.0
3
+ SuggestExtensions: false
4
+
5
+ Layout/LineLength:
6
+ Max: 120
7
+
8
+
9
+ Metrics/BlockLength:
10
+ Exclude:
11
+ - 'spec/**/*'
12
+
13
+ Style/Documentation:
14
+ Enabled: false
15
+
16
+ Style/ClassAndModuleChildren:
17
+ Enabled: false
18
+
19
+ Metrics/AbcSize:
20
+ Enabled: false
21
+
22
+ Metrics/MethodLength:
23
+ Enabled: false
24
+
25
+ Metrics/ClassLength:
26
+ Enabled: false
27
+
28
+ Metrics/CyclomaticComplexity:
29
+ Enabled: false
30
+
31
+ Metrics/PerceivedComplexity:
32
+ Enabled: false
33
+
34
+ Metrics/ParameterLists:
35
+ Enabled: false
36
+
37
+ Style/FrozenStringLiteralComment:
38
+ Enabled: false
39
+
40
+ Naming/FileName:
41
+ Enabled: false
42
+
43
+ Style/StringLiterals:
44
+ Enabled: false
45
+
46
+ Layout/SpaceInsideHashLiteralBraces:
47
+ Enabled: false
48
+
data/CHANGELOG.md CHANGED
@@ -1,71 +1,83 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [Unreleased]
9
-
10
- ## [1.3.3] - 2026-05-05
11
-
12
- ### Changed
13
-
14
- - **Ruby 4 and Graphiti 1.9.0 support**: Added support for Ruby 4 and Graphiti 1.9.0 and made minor performance improvements.
15
-
16
- ## [1.3.2] - 2026-02-11
17
-
18
- ### Changed
19
-
20
- - **Grouping criteria**: parsing improved to ignore commas inside parentheses, allowing for more complex grouping criteria (e.g. group_by='custom_function(arg1,arg2),another_value'). Previous implementation would split the criteria at the comma inside the parentheses, leading to inability to pass the function call. (PR #51)
21
-
22
- ## [1.3.1] - 2025-11-5
23
-
24
- ### Added
25
-
26
- - **Deserializer: relationship id(s) helpers**: introduced `relationship_id` and `relationship_ids` methods in `Deserializer` to fetch relationship id(s) by association name from request payload. (PR #49)
27
-
28
- ## [1.3.0] - 2025-10-13
29
-
30
- ### Added
31
-
32
- - **Extra fields preloading in main query**: Support preloading associations for `extra_fields` for main and sideloaded records. Adds `preload` option to `extra_attribute` in resource classes. Example: `extra_attribute :full_post_title, :string, preload: :author`. (PR #45)
33
- - **Extra fields preloading in separate query**: Collect IDs from the main query result and run a single separate query to load and calculate `extra_fields` values for all records. does not support preloading for deep sideloads. (PR #47)
34
-
35
- ## [1.2.0] - 2025-05-20
36
-
37
- ### Added
38
-
39
- - **Graphiti Compatibility**: Added support for Graphiti versions up to 1.8.x, fixing previously broken sideloading behavior.
40
-
41
- ### Fixed
42
-
43
- - **Serializer**: Resolved an issue where polymorphic resources were not functioning correctly in serializers.
44
-
45
- ## [1.1.0] - 2025-05-2
46
-
47
- ### Added
48
-
49
- - **Deep sideloading**: introduced Include scoping class to support deep sideloading using single query (e.g. include='author.posts')
50
- - **Deep sorting**: added SortNormalizer to support sorting by deep sideloaded resource's attribute (e.g. sort='author.posts.title')
51
- - **Links control**: Control the links(pagination and resource) in response via request query params (pagination_links=true|false, links=true|false)
52
-
53
- ## [1.0.0] - 2025-03-18
54
-
55
- ### Added
56
-
57
- - **MRI Support**: Introduced compatibility with MRI (Matz's Ruby Interpreter), expanding the gem's usability beyond JRuby (#38).
58
- - **Documentation Updates**: Expanded the README to provide clearer guidance on gem usage and integration (#38).
59
- - **Sideloading Workflow**: Enhanced scoping mechanisms and added support for eager loading associations to improve data retrieval efficiency and flexibility (#38).
60
-
61
- ### Changed
62
-
63
- - **Resource Class**: Instead of modifying the `Graphiti::Resource` class, we now define `Graphiti::ActiveGraph::Resource`,
64
- which must be inherited in all resource classes to enable ActiveGraph support (#38).
65
-
66
- ---
67
-
68
- *Note: For details on changes prior to version 1.0.0, please refer to the [`CHANGELOG_PRE_1.0.0.md`](CHANGELOG_PRE_1.0.0.md) file.*
69
-
70
- [unreleased]: https://github.com/mrhardikjoshi/graphiti-activegraph/compare/v1.0.0...master
71
- [1.0.0]: https://github.com/mrhardikjoshi/graphiti-activegraph/compare/9f837108ae57287c65b0f6fd2609dd56a95cd461...v1.0.0
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [1.4.0] - 2026-09-02
11
+
12
+ ### Changed
13
+
14
+ - Added support for Graphiti 1.13.4, including `Resource.wrap` and per-request relationship guards.
15
+
16
+ ### Fixed
17
+
18
+ - Forward Graphiti cache options, including `cache_tag`, through the ActiveGraph runner and resource proxy overrides.
19
+ - Keep statically unreadable relationships out of ActiveGraph serializers while preserving Graphiti 1.13's dynamic readability guards.
20
+ - Preserve Graphiti 1.13's scope resolution block and `around_persistence` mutation warning behavior in ActiveGraph overrides.
21
+
22
+ ## [1.3.3] - 2026-05-05
23
+
24
+ ### Changed
25
+
26
+ - **Ruby 4 and Graphiti 1.9.0 support**: Added support for Ruby 4 and Graphiti 1.9.0 and made minor performance improvements.
27
+
28
+ ## [1.3.2] - 2026-02-11
29
+
30
+ ### Changed
31
+
32
+ - **Grouping criteria**: parsing improved to ignore commas inside parentheses, allowing for more complex grouping criteria (e.g. group_by='custom_function(arg1,arg2),another_value'). Previous implementation would split the criteria at the comma inside the parentheses, leading to inability to pass the function call. (PR #51)
33
+
34
+ ## [1.3.1] - 2025-11-5
35
+
36
+ ### Added
37
+
38
+ - **Deserializer: relationship id(s) helpers**: introduced `relationship_id` and `relationship_ids` methods in `Deserializer` to fetch relationship id(s) by association name from request payload. (PR #49)
39
+
40
+ ## [1.3.0] - 2025-10-13
41
+
42
+ ### Added
43
+
44
+ - **Extra fields preloading in main query**: Support preloading associations for `extra_fields` for main and sideloaded records. Adds `preload` option to `extra_attribute` in resource classes. Example: `extra_attribute :full_post_title, :string, preload: :author`. (PR #45)
45
+ - **Extra fields preloading in separate query**: Collect IDs from the main query result and run a single separate query to load and calculate `extra_fields` values for all records. does not support preloading for deep sideloads. (PR #47)
46
+
47
+ ## [1.2.0] - 2025-05-20
48
+
49
+ ### Added
50
+
51
+ - **Graphiti Compatibility**: Added support for Graphiti versions up to 1.8.x, fixing previously broken sideloading behavior.
52
+
53
+ ### Fixed
54
+
55
+ - **Serializer**: Resolved an issue where polymorphic resources were not functioning correctly in serializers.
56
+
57
+ ## [1.1.0] - 2025-05-2
58
+
59
+ ### Added
60
+
61
+ - **Deep sideloading**: introduced Include scoping class to support deep sideloading using single query (e.g. include='author.posts')
62
+ - **Deep sorting**: added SortNormalizer to support sorting by deep sideloaded resource's attribute (e.g. sort='author.posts.title')
63
+ - **Links control**: Control the links(pagination and resource) in response via request query params (pagination_links=true|false, links=true|false)
64
+
65
+ ## [1.0.0] - 2025-03-18
66
+
67
+ ### Added
68
+
69
+ - **MRI Support**: Introduced compatibility with MRI (Matz's Ruby Interpreter), expanding the gem's usability beyond JRuby (#38).
70
+ - **Documentation Updates**: Expanded the README to provide clearer guidance on gem usage and integration (#38).
71
+ - **Sideloading Workflow**: Enhanced scoping mechanisms and added support for eager loading associations to improve data retrieval efficiency and flexibility (#38).
72
+
73
+ ### Changed
74
+
75
+ - **Resource Class**: Instead of modifying the `Graphiti::Resource` class, we now define `Graphiti::ActiveGraph::Resource`,
76
+ which must be inherited in all resource classes to enable ActiveGraph support (#38).
77
+
78
+ ---
79
+
80
+ *Note: For details on changes prior to version 1.0.0, please refer to the [`CHANGELOG_PRE_1.0.0.md`](CHANGELOG_PRE_1.0.0.md) file.*
81
+
82
+ [unreleased]: https://github.com/mrhardikjoshi/graphiti-activegraph/compare/v1.0.0...master
83
+ [1.0.0]: https://github.com/mrhardikjoshi/graphiti-activegraph/compare/9f837108ae57287c65b0f6fd2609dd56a95cd461...v1.0.0
@@ -1,35 +1,35 @@
1
- lib = File.expand_path('../lib', __FILE__)
2
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
- require 'graphiti/active_graph/version'
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = 'graphiti-activegraph'
7
- spec.version = Graphiti::ActiveGraph::VERSION
8
- spec.authors = ['Hardik Joshi']
9
- spec.email = ['hardikjoshi1991@gmail.com']
10
-
11
- spec.summary = 'Easily build jsonapi.org-compatible APIs for GraphDB'
12
- spec.homepage = 'https://github.com/mrhardikjoshi/graphiti-activegraph'
13
- spec.license = 'MIT'
14
-
15
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
- spec.bindir = 'exe'
17
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
- spec.require_paths = ['lib']
19
-
20
- spec.add_dependency 'graphiti', '>= 1.6.4', '<= 1.9.0'
21
- spec.add_dependency 'activegraph', '>= 12.0.0.beta.5'
22
- spec.add_dependency 'parslet', '>= 2.0.0'
23
- spec.add_dependency 'activegraph-extensions', '>= 0.1.0'
24
-
25
- spec.add_development_dependency 'graphiti_spec_helpers', '>= 1.0.0'
26
- spec.add_development_dependency 'standard'
27
- spec.add_development_dependency 'pry'
28
- spec.add_development_dependency 'ffaker'
29
- spec.add_development_dependency 'simplecov'
30
- spec.add_development_dependency 'simplecov_json_formatter'
31
- spec.add_development_dependency 'factory_bot_rails'
32
- spec.add_development_dependency 'rake', '>= 10.0'
33
- spec.add_development_dependency 'rspec', '>= 3.9.0'
34
- spec.add_development_dependency 'ostruct'
35
- end
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'graphiti/active_graph/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'graphiti-activegraph'
7
+ spec.version = Graphiti::ActiveGraph::VERSION
8
+ spec.authors = ['Hardik Joshi']
9
+ spec.email = ['hardikjoshi1991@gmail.com']
10
+
11
+ spec.summary = 'Easily build jsonapi.org-compatible APIs for GraphDB'
12
+ spec.homepage = 'https://github.com/mrhardikjoshi/graphiti-activegraph'
13
+ spec.license = 'MIT'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.bindir = 'exe'
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_dependency 'graphiti', '>= 1.9.0', '<= 1.13.4'
21
+ spec.add_dependency 'activegraph', '>= 12.0.0.beta.5'
22
+ spec.add_dependency 'parslet', '>= 2.0.0'
23
+ spec.add_dependency 'activegraph-extensions', '>= 0.1.0'
24
+
25
+ spec.add_development_dependency 'graphiti_spec_helpers', '>= 1.0.0'
26
+ spec.add_development_dependency 'standard'
27
+ spec.add_development_dependency 'pry'
28
+ spec.add_development_dependency 'ffaker'
29
+ spec.add_development_dependency 'simplecov'
30
+ spec.add_development_dependency 'simplecov_json_formatter'
31
+ spec.add_development_dependency 'factory_bot_rails'
32
+ spec.add_development_dependency 'rake', '>= 10.0'
33
+ spec.add_development_dependency 'rspec', '>= 3.9.0'
34
+ spec.add_development_dependency 'ostruct'
35
+ end
@@ -1,86 +1,97 @@
1
- module Graphiti::ActiveGraph
2
- module ResourceProxy
3
- include Graphiti::ActiveGraph::SideloadResolve
4
- attr_reader :preloaded
5
-
6
- def initialize(resource, scope, query,
7
- payload: nil,
8
- single: false,
9
- raise_on_missing: false,
10
- preloaded: false)
11
- @resource = resource
12
- @scope = scope
13
- @query = query
14
- @payload = payload
15
- @single = single
16
- @raise_on_missing = raise_on_missing
17
- @preloaded = preloaded
18
- end
19
-
20
- def data
21
- return @data if @data
22
-
23
- return super unless @preloaded
24
-
25
- resolve_sideloads(@preloaded)
26
- @single ? data_for_preloaded_record : data_for_preloaded_records
27
- end
28
-
29
- alias_method :resolve_data, :data
30
-
31
- def data_for_preloaded_record
32
- @preloaded = @preloaded.is_a?(Array) ? @preloaded[0] : @preloaded
33
- @resource.decorate_record(@preloaded)
34
- @data = @preloaded
35
- end
36
-
37
- def data_for_preloaded_records
38
- @preloaded.each do |r|
39
- @resource.decorate_record(r)
40
- end
41
- @data = @preloaded
42
- end
43
-
44
- def stats
45
- @stats ||= if @query.stats && !resource.relation_resource?
46
- payload = ::Graphiti::Stats::Payload.new @resource,
47
- @query,
48
- @scope.unpaginated_object,
49
- data
50
- payload.generate
51
- else
52
- {}
53
- end
54
- end
55
-
56
- def save(action: :create)
57
- # TODO: remove this. Only used for persisting many-to-many with AR
58
- # (see activerecord adapter)
59
- original = Graphiti.context[:namespace]
60
- begin
61
- Graphiti.context[:namespace] = action
62
- ::Graphiti::RequestValidator.new(@resource, @payload.params, action).validate!
63
- validator = persist {
64
- @resource.persist_with_relationships \
65
- @payload.meta(action: action),
66
- @payload.attributes,
67
- @payload.relationships
68
- }
69
- ensure
70
- Graphiti.context[:namespace] = original
71
- end
72
- @data, success = validator.to_a
73
-
74
- if success && !resource.relation_resource?
75
- # If the context namespace is `update` or `create`, certain
76
- # adapters will cause N+1 validation calls, so lets explicitly
77
- # switch to a lookup context.
78
- Graphiti.with_context(Graphiti.context[:object], :show) do
79
- @scope.resolve_sideloads([@data])
80
- end
81
- end
82
-
83
- success
84
- end
85
- end
86
- end
1
+ module Graphiti::ActiveGraph
2
+ module ResourceProxy
3
+ include Graphiti::ActiveGraph::SideloadResolve
4
+
5
+ attr_reader :preloaded, :cache_tag
6
+
7
+ def initialize(
8
+ resource,
9
+ scope,
10
+ query,
11
+ payload: nil,
12
+ single: false,
13
+ raise_on_missing: false,
14
+ cache: nil,
15
+ cache_expires_in: nil,
16
+ cache_tag: nil,
17
+ preloaded: false
18
+ )
19
+ @resource = resource
20
+ @scope = scope
21
+ @query = query
22
+ @payload = payload
23
+ @single = single
24
+ @raise_on_missing = raise_on_missing
25
+ @cache = cache
26
+ @cache_expires_in = cache_expires_in
27
+ @cache_tag = cache_tag
28
+ @preloaded = preloaded
29
+ end
30
+
31
+ def data
32
+ return @data if @data
33
+
34
+ return super unless @preloaded
35
+
36
+ resolve_sideloads(@preloaded)
37
+ @single ? data_for_preloaded_record : data_for_preloaded_records
38
+ end
39
+
40
+ alias_method :resolve_data, :data
41
+
42
+ def data_for_preloaded_record
43
+ @preloaded = @preloaded.is_a?(Array) ? @preloaded[0] : @preloaded
44
+ @resource.decorate_record(@preloaded)
45
+ @data = @preloaded
46
+ end
47
+
48
+ def data_for_preloaded_records
49
+ @preloaded.each do |r|
50
+ @resource.decorate_record(r)
51
+ end
52
+ @data = @preloaded
53
+ end
54
+
55
+ def stats
56
+ @stats ||= if @query.stats && !resource.relation_resource?
57
+ payload = ::Graphiti::Stats::Payload.new @resource,
58
+ @query,
59
+ @scope.unpaginated_object,
60
+ data
61
+ payload.generate
62
+ else
63
+ {}
64
+ end
65
+ end
66
+
67
+ def save(action: :create)
68
+ # TODO: remove this. Only used for persisting many-to-many with AR
69
+ # (see activerecord adapter)
70
+ original = Graphiti.context[:namespace]
71
+ begin
72
+ Graphiti.context[:namespace] = action
73
+ ::Graphiti::RequestValidator.new(@resource, @payload.params, action).validate!
74
+ validator = persist {
75
+ @resource.persist_with_relationships \
76
+ @payload.meta(action: action),
77
+ @payload.attributes,
78
+ @payload.relationships
79
+ }
80
+ ensure
81
+ Graphiti.context[:namespace] = original
82
+ end
83
+ @data, success = validator.to_a
84
+
85
+ if success && !resource.relation_resource?
86
+ # If the context namespace is `update` or `create`, certain
87
+ # adapters will cause N+1 validation calls, so lets explicitly
88
+ # switch to a lookup context.
89
+ Graphiti.with_context(Graphiti.context[:object], :show) do
90
+ @scope.resolve_sideloads([@data])
91
+ end
92
+ end
93
+
94
+ success
95
+ end
96
+ end
97
+ end
@@ -1,25 +1,31 @@
1
- module Graphiti::ActiveGraph
2
- module Resources
3
- module Persistence
4
- def update(update_params, meta = nil)
5
- model_instance = nil
6
- id = update_params[:id]
7
- update_params = update_params.except(:id)
8
-
9
- run_callbacks :persistence, :update, update_params, meta do
10
- run_callbacks :attributes, :update, update_params, meta do |params|
11
- model_instance = id ? model.find(id) : self.class._find(id: id).data
12
- call_with_meta(:assign_attributes, model_instance, params, meta)
13
- model_instance
14
- end
15
-
16
- run_callbacks :save, :update, model_instance, meta do
17
- model_instance = call_with_meta(:save, model_instance, meta)
18
- end
19
- end
20
-
21
- model_instance
22
- end
23
- end
24
- end
25
- end
1
+ module Graphiti::ActiveGraph
2
+ module Resources
3
+ module Persistence
4
+ def update(update_params, meta = nil)
5
+ model_instance = nil
6
+ id = update_params[:id]
7
+ update_params = update_params.except(:id)
8
+
9
+ snapshot = attributes_snapshot(:update, update_params) if respond_to?(:attributes_snapshot, true)
10
+
11
+ run_callbacks :persistence, :update, update_params, meta do
12
+ if respond_to?(:warn_attributes_mutated_in_around_persistence, true)
13
+ warn_attributes_mutated_in_around_persistence(:update, snapshot, update_params)
14
+ end
15
+
16
+ run_callbacks :attributes, :update, update_params, meta do |params|
17
+ model_instance = id ? model.find(id) : self.class._find(id: id).data
18
+ call_with_meta(:assign_attributes, model_instance, params, meta)
19
+ model_instance
20
+ end
21
+
22
+ run_callbacks :save, :update, model_instance, meta do
23
+ model_instance = call_with_meta(:save, model_instance, meta)
24
+ end
25
+ end
26
+
27
+ model_instance
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,39 +1,47 @@
1
- module Graphiti::ActiveGraph
2
- module Runner
3
- def initialize(resource_class, params, query = nil, action = nil)
4
- @resource_class = resource_class
5
- @params = params
6
- @query = query
7
- @action = action
8
-
9
- validator = ::Graphiti::RequestValidator.new(jsonapi_resource, params, action)
10
-
11
- validator.validate! unless params[:skip_render_val]
12
-
13
- @deserialized_payload = validator.deserialized_payload
14
- end
15
-
16
- def proxy(base = nil, opts = {})
17
- base ||= jsonapi_resource.base_scope
18
- scope_opts = opts.slice(:sideload_parent_length,
19
- :default_paginate,
20
- :after_resolve,
21
- :sideload,
22
- :parent,
23
- :params,
24
- :preloaded).merge(unpaginated_query: params[:unpaginated_query])
25
- scope = jsonapi_scope(base, scope_opts)
26
- preloaded = opts[:preloaded]
27
- options = { payload: deserialized_payload,
28
- single: opts[:single],
29
- raise_on_missing: opts[:raise_on_missing],
30
- preloaded: preloaded
31
- }
32
- ::Graphiti::ResourceProxy.new jsonapi_resource,
33
- scope,
34
- query,
35
- **options
36
- end
37
- end
38
- end
39
-
1
+ module Graphiti::ActiveGraph
2
+ module Runner
3
+ def initialize(resource_class, params, query = nil, action = nil)
4
+ @resource_class = resource_class
5
+ @params = params
6
+ @query = query
7
+ @action = action
8
+
9
+ validator = ::Graphiti::RequestValidator.new(jsonapi_resource, params, action)
10
+
11
+ validator.validate! unless params[:skip_render_val]
12
+
13
+ @deserialized_payload = validator.deserialized_payload
14
+ end
15
+
16
+ def proxy(base = nil, opts = {})
17
+ base ||= jsonapi_resource.base_scope
18
+ scope_opts = opts.slice(
19
+ :sideload_parent_length,
20
+ :default_paginate,
21
+ :after_resolve,
22
+ :sideload,
23
+ :parent,
24
+ :params,
25
+ :bypass_required_filters,
26
+ :preloaded
27
+ ).merge(unpaginated_query: params[:unpaginated_query])
28
+ scope = jsonapi_scope(base, scope_opts)
29
+ preloaded = opts[:preloaded]
30
+ options = {
31
+ payload: deserialized_payload,
32
+ single: opts[:single],
33
+ raise_on_missing: opts[:raise_on_missing],
34
+ cache: opts[:cache],
35
+ cache_expires_in: opts[:cache_expires_in],
36
+ cache_tag: opts[:cache_tag],
37
+ preloaded: preloaded
38
+ }
39
+ ::Graphiti::ResourceProxy.new(
40
+ jsonapi_resource,
41
+ scope,
42
+ query,
43
+ **options
44
+ )
45
+ end
46
+ end
47
+ end
@@ -1,119 +1,119 @@
1
- module Graphiti::ActiveGraph
2
- module SideloadResolve
3
- PRELOAD_METHOD_PREFIX = 'preload_'.freeze
4
-
5
- def initialize(object, resource, query, opts = {})
6
- @object = object
7
- @resource = resource
8
- @query = query
9
- @opts = opts
10
- @unpaginated_object = opts[:unpaginated_query].presence || @object
11
-
12
- return if opts[:preloaded]
13
- @object = @resource.around_scoping(@object, @query.hash) { |scope|
14
- apply_scoping(scope, opts)
15
- }
16
- end
17
-
18
- def resolve_sideloads(parents)
19
- end
20
-
21
- def resolve
22
- resolve_with_callbacks.tap { |results| preload_extra_fields(results) }
23
- end
24
-
25
- private
26
-
27
- def resolve_with_callbacks
28
- if @query.zero_results?
29
- []
30
- else
31
- resolved = broadcast_data { |payload|
32
- @object = @resource.before_resolve(@object, @query)
33
- payload[:results] = @resource.resolve(@object)
34
- payload[:results]
35
- }
36
- resolved.compact!
37
- assign_serializer(resolved)
38
- yield resolved if block_given?
39
- @opts[:after_resolve]&.call(resolved)
40
- resolve_sideloads(resolved) unless @query.sideloads.empty?
41
- resolved
42
- end
43
- end
44
-
45
- def preload_extra_fields(results)
46
- @query.extra_fields.each do |type, extra_field_names|
47
- extra_field_names.each do |name|
48
- next unless preload_extra_field?(type, name)
49
-
50
- records_for_preload = collect_records_for_preload(type, results)
51
- result_map = fetch_preloaded_data(type, name, records_for_preload)
52
- assign_preloaded_data(records_for_preload, name, result_map)
53
- end
54
- end
55
- end
56
-
57
- def fetch_preloaded_data(type, extra_field_name, results)
58
- resource_for_preload(type).model.public_send(default_preload_method(extra_field_name), results.pluck(:id).uniq)
59
- end
60
-
61
- def assign_preloaded_data(results, extra_field_name, result_map)
62
- results.each { |r| r.public_send("#{extra_field_name}=", result_map[r.id]) }
63
- end
64
-
65
- def preload_extra_field?(type, extra_field_name)
66
- resource = resource_for_preload(type)
67
- resource && resource.extra_attribute?(extra_field_name) && resource.model.respond_to?(default_preload_method(extra_field_name))
68
- end
69
-
70
- def resource_for_preload(type)
71
- return @resource if type == @resource.type
72
-
73
- find_resource_in_included_associations(type) unless @query.sideloads.empty?
74
- end
75
-
76
- def find_resource_in_included_associations(type, sideload_query = @query)
77
- sideload_query.sideloads.values.each do |sideload|
78
- return sideload.resource if sideload.resource.type == type
79
-
80
- resource = find_resource_in_included_associations(type, sideload)
81
- return resource if resource
82
- end
83
-
84
- nil
85
- end
86
-
87
- def default_preload_method(extra_field_name)
88
- "#{PRELOAD_METHOD_PREFIX}#{extra_field_name}"
89
- end
90
-
91
- def collect_records_for_preload(type, results)
92
- base_records = resource_matches_type?(@resource, type) ? Array(results) : []
93
- sideloaded_records = collect_sideloaded_records(Array(results), @query, type)
94
- (base_records + sideloaded_records).flatten.compact.uniq
95
- end
96
-
97
- def collect_sideloaded_records(source_records, sideload_query, type)
98
- return [] if source_records.empty? || sideload_query.sideloads.empty?
99
-
100
- sideload_query.sideloads.flat_map do |sideload_name, nested_query|
101
- associated_records = collect_associated_records(source_records, sideload_name)
102
- matched_records = resource_matches_type?(nested_query.resource, type) ? associated_records : []
103
- matched_records + collect_sideloaded_records(associated_records, nested_query, type)
104
- end
105
- end
106
-
107
- def resource_matches_type?(resource, type)
108
- resource&.type == type
109
- end
110
-
111
- def collect_associated_records(source_records, sideload_name)
112
- source_records.flat_map do |parent|
113
- next [] unless parent.respond_to?(sideload_name)
114
-
115
- Array(parent.public_send(sideload_name)).compact
116
- end
117
- end
118
- end
119
- end
1
+ module Graphiti::ActiveGraph
2
+ module SideloadResolve
3
+ PRELOAD_METHOD_PREFIX = 'preload_'.freeze
4
+
5
+ def initialize(object, resource, query, opts = {})
6
+ @object = object
7
+ @resource = resource
8
+ @query = query
9
+ @opts = opts
10
+ @unpaginated_object = opts[:unpaginated_query].presence || @object
11
+
12
+ return if opts[:preloaded]
13
+ @object = @resource.around_scoping(@object, @query.hash) { |scope|
14
+ apply_scoping(scope, opts)
15
+ }
16
+ end
17
+
18
+ def resolve_sideloads(parents)
19
+ end
20
+
21
+ def resolve(&blk)
22
+ resolve_with_callbacks(&blk).tap { |results| preload_extra_fields(results) }
23
+ end
24
+
25
+ private
26
+
27
+ def resolve_with_callbacks
28
+ if @query.zero_results?
29
+ []
30
+ else
31
+ resolved = broadcast_data { |payload|
32
+ @object = @resource.before_resolve(@object, @query)
33
+ payload[:results] = @resource.resolve(@object)
34
+ payload[:results]
35
+ }
36
+ resolved.compact!
37
+ assign_serializer(resolved)
38
+ yield resolved if block_given?
39
+ @opts[:after_resolve]&.call(resolved)
40
+ resolve_sideloads(resolved) unless @query.sideloads.empty?
41
+ resolved
42
+ end
43
+ end
44
+
45
+ def preload_extra_fields(results)
46
+ @query.extra_fields.each do |type, extra_field_names|
47
+ extra_field_names.each do |name|
48
+ next unless preload_extra_field?(type, name)
49
+
50
+ records_for_preload = collect_records_for_preload(type, results)
51
+ result_map = fetch_preloaded_data(type, name, records_for_preload)
52
+ assign_preloaded_data(records_for_preload, name, result_map)
53
+ end
54
+ end
55
+ end
56
+
57
+ def fetch_preloaded_data(type, extra_field_name, results)
58
+ resource_for_preload(type).model.public_send(default_preload_method(extra_field_name), results.pluck(:id).uniq)
59
+ end
60
+
61
+ def assign_preloaded_data(results, extra_field_name, result_map)
62
+ results.each { |r| r.public_send("#{extra_field_name}=", result_map[r.id]) }
63
+ end
64
+
65
+ def preload_extra_field?(type, extra_field_name)
66
+ resource = resource_for_preload(type)
67
+ resource && resource.extra_attribute?(extra_field_name) && resource.model.respond_to?(default_preload_method(extra_field_name))
68
+ end
69
+
70
+ def resource_for_preload(type)
71
+ return @resource if type == @resource.type
72
+
73
+ find_resource_in_included_associations(type) unless @query.sideloads.empty?
74
+ end
75
+
76
+ def find_resource_in_included_associations(type, sideload_query = @query)
77
+ sideload_query.sideloads.values.each do |sideload|
78
+ return sideload.resource if sideload.resource.type == type
79
+
80
+ resource = find_resource_in_included_associations(type, sideload)
81
+ return resource if resource
82
+ end
83
+
84
+ nil
85
+ end
86
+
87
+ def default_preload_method(extra_field_name)
88
+ "#{PRELOAD_METHOD_PREFIX}#{extra_field_name}"
89
+ end
90
+
91
+ def collect_records_for_preload(type, results)
92
+ base_records = resource_matches_type?(@resource, type) ? Array(results) : []
93
+ sideloaded_records = collect_sideloaded_records(Array(results), @query, type)
94
+ (base_records + sideloaded_records).flatten.compact.uniq
95
+ end
96
+
97
+ def collect_sideloaded_records(source_records, sideload_query, type)
98
+ return [] if source_records.empty? || sideload_query.sideloads.empty?
99
+
100
+ sideload_query.sideloads.flat_map do |sideload_name, nested_query|
101
+ associated_records = collect_associated_records(source_records, sideload_name)
102
+ matched_records = resource_matches_type?(nested_query.resource, type) ? associated_records : []
103
+ matched_records + collect_sideloaded_records(associated_records, nested_query, type)
104
+ end
105
+ end
106
+
107
+ def resource_matches_type?(resource, type)
108
+ resource&.type == type
109
+ end
110
+
111
+ def collect_associated_records(source_records, sideload_name)
112
+ source_records.flat_map do |parent|
113
+ next [] unless parent.respond_to?(sideload_name)
114
+
115
+ Array(parent.public_send(sideload_name)).compact
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Graphiti
4
+ module ActiveGraph
5
+ module Util
6
+ module SerializerRelationships
7
+ private
8
+
9
+ def apply?(sideload)
10
+ return super if dynamically_readable?(sideload)
11
+
12
+ super && sideload.readable?
13
+ end
14
+
15
+ def dynamically_readable?(sideload)
16
+ flag = sideload.instance_variable_get(:@readable)
17
+ flag.is_a?(Symbol) || flag.is_a?(String) || flag.is_a?(Proc)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,5 +1,7 @@
1
- module Graphiti
2
- module ActiveGraph
3
- VERSION = '1.3.3'
4
- end
5
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Graphiti
4
+ module ActiveGraph
5
+ VERSION = "1.4.0"
6
+ end
7
+ end
@@ -1,43 +1,44 @@
1
- require 'active_support'
2
- require 'active_graph'
3
-
4
- # Workaround for jruby prepend issue https://github.com/jruby/jruby/issues/6971
5
- module Graphiti
6
- module ActiveGraph
7
- end
8
- module Scoping
9
- end
10
- end
11
- require 'graphiti/scoping/filterable'
12
- require 'graphiti/resource/persistence'
13
- require 'graphiti/resource/interface'
14
- # End workaround for jruby prepend issue
15
-
16
- require 'zeitwerk'
17
- loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
18
- loader.inflector.inflect 'version' => 'VERSION'
19
- loader.ignore(File.expand_path('graphiti-activegraph.rb', __dir__))
20
- loader.setup
21
-
22
- Graphiti::Scoping::Filterable.prepend Graphiti::ActiveGraph::Scoping::Filterable
23
- Graphiti::Resource::Persistence.prepend Graphiti::ActiveGraph::Resources::Persistence
24
- Graphiti::Resource::Interface::ClassMethods.prepend Graphiti::ActiveGraph::Resources::Interface::ClassMethods
25
- require 'graphiti'
26
- Graphiti::Scoping::Filter.prepend Graphiti::ActiveGraph::Scoping::Filter
27
- Graphiti::Serializer.prepend Graphiti::ActiveGraph::Serializer
28
- Graphiti::Util::SerializerRelationship.prepend Graphiti::ActiveGraph::Util::SerializerRelationship
29
- Graphiti::Util::SerializerAttribute.prepend Graphiti::ActiveGraph::Util::SerializerAttribute
30
- Graphiti::Util::RelationshipPayload.prepend Graphiti::ActiveGraph::Util::RelationshipPayload
31
- Graphiti::Query.prepend Graphiti::ActiveGraph::Query
32
- Graphiti::ResourceProxy.prepend Graphiti::ActiveGraph::ResourceProxy
33
- Graphiti::Runner.prepend Graphiti::ActiveGraph::Runner
34
- Graphiti::Scope.prepend Graphiti::ActiveGraph::SideloadResolve
35
- Graphiti::Configuration.include Graphiti::SidepostConfiguration
36
- Graphiti::RequestValidators::Validator.prepend Graphiti::ActiveGraph::RequestValidators::Validator
37
-
38
- unless RUBY_PLATFORM == 'java'
39
- Graphiti.prepend Graphiti::ActiveGraph::Extensions::Context
40
- end
41
-
42
- # JSONAPI extensions
43
- JSONAPI::Serializable::Resource.prepend Graphiti::ActiveGraph::JsonapiExt::Serializable::ResourceExt
1
+ require 'active_support'
2
+ require 'active_graph'
3
+
4
+ # Workaround for jruby prepend issue https://github.com/jruby/jruby/issues/6971
5
+ module Graphiti
6
+ module ActiveGraph
7
+ end
8
+ module Scoping
9
+ end
10
+ end
11
+ require 'graphiti/scoping/filterable'
12
+ require 'graphiti/resource/persistence'
13
+ require 'graphiti/resource/interface'
14
+ # End workaround for jruby prepend issue
15
+
16
+ require 'zeitwerk'
17
+ loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
18
+ loader.inflector.inflect 'version' => 'VERSION'
19
+ loader.ignore(File.expand_path('graphiti-activegraph.rb', __dir__))
20
+ loader.setup
21
+
22
+ Graphiti::Scoping::Filterable.prepend Graphiti::ActiveGraph::Scoping::Filterable
23
+ Graphiti::Resource::Persistence.prepend Graphiti::ActiveGraph::Resources::Persistence
24
+ Graphiti::Resource::Interface::ClassMethods.prepend Graphiti::ActiveGraph::Resources::Interface::ClassMethods
25
+ require 'graphiti'
26
+ Graphiti::Scoping::Filter.prepend Graphiti::ActiveGraph::Scoping::Filter
27
+ Graphiti::Serializer.prepend Graphiti::ActiveGraph::Serializer
28
+ Graphiti::Util::SerializerRelationships.prepend Graphiti::ActiveGraph::Util::SerializerRelationships
29
+ Graphiti::Util::SerializerRelationship.prepend Graphiti::ActiveGraph::Util::SerializerRelationship
30
+ Graphiti::Util::SerializerAttribute.prepend Graphiti::ActiveGraph::Util::SerializerAttribute
31
+ Graphiti::Util::RelationshipPayload.prepend Graphiti::ActiveGraph::Util::RelationshipPayload
32
+ Graphiti::Query.prepend Graphiti::ActiveGraph::Query
33
+ Graphiti::ResourceProxy.prepend Graphiti::ActiveGraph::ResourceProxy
34
+ Graphiti::Runner.prepend Graphiti::ActiveGraph::Runner
35
+ Graphiti::Scope.prepend Graphiti::ActiveGraph::SideloadResolve
36
+ Graphiti::Configuration.include Graphiti::SidepostConfiguration
37
+ Graphiti::RequestValidators::Validator.prepend Graphiti::ActiveGraph::RequestValidators::Validator
38
+
39
+ unless RUBY_PLATFORM == 'java'
40
+ Graphiti.prepend Graphiti::ActiveGraph::Extensions::Context
41
+ end
42
+
43
+ # JSONAPI extensions
44
+ JSONAPI::Serializable::Resource.prepend Graphiti::ActiveGraph::JsonapiExt::Serializable::ResourceExt
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphiti-activegraph
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.3
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hardik Joshi
@@ -15,20 +15,20 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: 1.6.4
18
+ version: 1.9.0
19
19
  - - "<="
20
20
  - !ruby/object:Gem::Version
21
- version: 1.9.0
21
+ version: 1.13.4
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
26
  - - ">="
27
27
  - !ruby/object:Gem::Version
28
- version: 1.6.4
28
+ version: 1.9.0
29
29
  - - "<="
30
30
  - !ruby/object:Gem::Version
31
- version: 1.9.0
31
+ version: 1.13.4
32
32
  - !ruby/object:Gem::Dependency
33
33
  name: activegraph
34
34
  requirement: !ruby/object:Gem::Requirement
@@ -274,6 +274,7 @@ files:
274
274
  - lib/graphiti/active_graph/util/relationship_payload.rb
275
275
  - lib/graphiti/active_graph/util/serializer_attribute.rb
276
276
  - lib/graphiti/active_graph/util/serializer_relationship.rb
277
+ - lib/graphiti/active_graph/util/serializer_relationships.rb
277
278
  - lib/graphiti/active_graph/util/transformers/relation_param.rb
278
279
  - lib/graphiti/active_graph/version.rb
279
280
  - lib/graphiti/sidepost_configuration.rb