Skip to content

Commit c913fd3

Browse files
made relationship_id(s) extractable (#49)
* made relationship_id(s) extractable * Qltysh in github actions (rubocop and code coverage) * bumped version and updated documentation
1 parent 9804525 commit c913fd3

File tree

11 files changed

+122
-7
lines changed

11 files changed

+122
-7
lines changed

.github/workflows/specs.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ on:
1010
workflow_dispatch:
1111

1212
permissions:
13+
actions: write
1314
contents: read
1415

1516
jobs:
1617
test:
1718
runs-on: ubuntu-latest
19+
permissions:
20+
id-token: write
1821
strategy:
1922
fail-fast: false
2023
matrix:
@@ -49,3 +52,8 @@ jobs:
4952

5053
- name: Run tests
5154
run: bundle exec rspec
55+
56+
- uses: qltysh/qlty-action/coverage@v1
57+
with:
58+
oidc: true
59+
files: coverage/coverage.json

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [1.3.1] - 2025-11-5
11+
### Added
12+
- **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)
13+
1014
## [1.3.0] - 2025-10-13
1115
### Added
1216
- **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)

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ graphiti-activegraph allows assigning and unassigning relationships via sidepost
4545
Graphiti stores context using `Thread.current[]`, which does not persist across different fibers within the same thread. In graphiti-activegraph, when running on MRI (non-JRuby environments), the gem uses `thread_variable_get` and `thread_variable_set`. Ensuring the context remains consistent across different fibers in the same thread.
4646

4747
### New Features in graphiti-activegraph
48+
For detailed API documentation and helper method behavior, see the [docs](./docs) directory.
49+
- [Deserializer](./docs/deserializer.md)
50+
- [Adapter](./docs/adapter.md) *(planned)*
51+
- [Resource](./docs/resource.md) *(planned)*
52+
4853
#### Rendering Preloaded Objects Without Extra Queries
4954
graphiti-activegraph introduces two new methods on the Graphiti resource class:
5055
`with_preloaded_obj(record, params)` – Renders a single preloaded ActiveGraph object without querying the database.
@@ -107,6 +112,8 @@ Currently, this feature does not support preloading for deep sideloads such as `
107112

108113
Check [spec/support/factory_bot_setup.rb](https://github.com/mrhardikjoshi/graphiti-activegraph/blob/master/spec/support/factory_bot_setup.rb) and [spec/active_graph/sideload_resolve_spec.rb](https://github.com/mrhardikjoshi/graphiti-activegraph/blob/master/spec/active_graph/sideload_resolve_spec.rb) for examples of usage.
109114

115+
## [CHANGELOG](CHANGELOG.md)
116+
110117
## Contributing
111118
Bug reports and pull requests are welcome on GitHub at https://github.com/mrhardikjoshi/graphiti-activegraph. This project is intended to be a safe, welcoming space for collaboration.
112119

docs/deserializer.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Deserializer
2+
3+
### Overview
4+
Handles incoming JSON:API payloads for Graphiti. Deserializes the request body and provides helper methods to extract and reconcile data, relationships and meta.
5+
6+
### Methods
7+
8+
#### `relationship_id(name)`
9+
Returns the single related id for the given association name.
10+
Used when the relationship is `has_one`.
11+
12+
#### `relationship_ids(name)`
13+
Returns an array of related ids for the given association name.
14+
Used when the relationship is `has_many`.
15+
16+
#### `add_path_id_to_relationships!(params)`
17+
Ensures that relationship ids passed in the request path are reflected in `params[:data][:relationships]` when missing in the request body.
18+
If the request body already contains an id for the same relationship, it compares the path and body ids using `detect_conflict` to prevent mismatched identifiers.
19+
The method is idempotent per request—subsequent calls are ignored once processed.
20+
21+
**Behavior summary:**
22+
- Adds missing relationship ids to the request body.
23+
- Detects and surfaces id mismatches between path and body.
24+
- Updates the internal `relationships` hash for consistency.
25+
- Marks the deserializer as updated to avoid repeated execution.
26+
27+
**Parameters:**
28+
- `params` — Hash (usually the request parameters).
29+
30+
**Returns:**
31+
- Modified `params` Hash with relationship ids added where necessary.
32+
33+
**Example:**
34+
```ruby
35+
# Given a path like /authors/42/books
36+
path_map = { author: "42" }
37+
params = { data: { type: "books", attributes: { title: "Graph Databases" } } }
38+
39+
deserializer.add_path_id_to_relationships!(params)
40+
# => params[:data][:relationships][:author][:data][:id] == "42"

graphiti-activegraph.gemspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ Gem::Specification.new do |spec|
2626
spec.add_development_dependency 'standard'
2727
spec.add_development_dependency 'pry'
2828
spec.add_development_dependency 'ffaker'
29+
spec.add_development_dependency 'simplecov'
30+
spec.add_development_dependency 'simplecov_json_formatter'
2931
spec.add_development_dependency 'factory_bot_rails'
3032
spec.add_development_dependency 'rake', '>= 10.0'
3133
spec.add_development_dependency 'rspec', '>= 3.9.0'
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module Graphiti::ActiveGraph::Concerns
2+
module Relationships
3+
def relationship?(name)
4+
relationships[name.to_sym].present?
5+
end
6+
7+
def relationship_id(name)
8+
relationships[name]&.dig(:attributes, :id)
9+
end
10+
11+
def relationship_ids(name)
12+
Array.wrap(relationships[name]).pluck(:attributes).pluck(:id)
13+
end
14+
end
15+
end

lib/graphiti/active_graph/deserializer.rb

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module Graphiti::ActiveGraph
22
class Deserializer < Graphiti::Deserializer
33
include Concerns::PathRelationships
4+
include Concerns::Relationships
45

56
class Conflict < StandardError
67
attr_reader :key, :path_value, :body_value
@@ -55,10 +56,6 @@ def process_relationships(relationship_hash)
5556
end
5657
end
5758

58-
def relationship?(name)
59-
relationships[name.to_sym].present?
60-
end
61-
6259
# change empty relationship as `disassociate` hash so they will be removed
6360
def process_nil_relationship(name)
6461
attributes = {}

lib/graphiti/active_graph/extensions/query_dsl/performer.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ def query_generator
2323
end
2424

2525
def query_generator_config
26-
require 'pry'
27-
binding.pry
2826
{
2927
query: scope,
3028
with_vars_to_carry:,
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module Graphiti
22
module ActiveGraph
3-
VERSION = '1.3.0'
3+
VERSION = '1.3.1'
44
end
55
end

spec/active_graph/deserializer_spec.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,37 @@
3939
end
4040
end
4141

42+
describe '#relationship_id' do
43+
let(:rel_data) { { data: { id: 1, type: 'satellites' } } }
44+
let(:params) { { data: { relationships: { satellites: rel_data } } }.with_indifferent_access }
45+
subject { deserializer.relationship_id(:satellites) }
46+
47+
it { is_expected.to be 1 }
48+
49+
context 'with no relationship in payload' do
50+
let(:params) { { data: {'type': 'planet'} } }
51+
it { is_expected.to be nil }
52+
end
53+
end
54+
55+
describe '#relationship_ids' do
56+
let(:rel_data) { { data: [{ id: 1, type: 'satellites' }] } }
57+
let(:params) { { data: { relationships: { satellites: rel_data } } }.with_indifferent_access }
58+
subject { deserializer.relationship_ids(:satellites) }
59+
60+
it { is_expected.to contain_exactly(1) }
61+
62+
context 'with multiple relationships in payload' do
63+
let(:rel_data) { { data: [{ id: 1, type: 'satellites' }, { id: 29, type: 'satellites' }] } }
64+
it { is_expected.to contain_exactly(1, 29) }
65+
end
66+
67+
context 'with no relationship in payload' do
68+
let(:params) { { data: {'type': 'planet'} } }
69+
it { is_expected.to be_empty }
70+
end
71+
end
72+
4273
describe '#relationship?' do
4374
subject { deserializer.relationship?(rel_name) }
4475

0 commit comments

Comments
 (0)