graphiti 1.8.0 → 1.8.2

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: 5530b650ab32f95fd4ed881afc07d68b5a252fa416959f19e8cced55de5266d2
4
- data.tar.gz: 3054b7eb3cf13f3e6a744690fbc95ef38b010e6f8e7f4f6b9365696e9395c07a
3
+ metadata.gz: 694b6036077d9ea14373be4b15e8e2d9cfab59253d79ce36d85303aa182a5482
4
+ data.tar.gz: 64afa2d52f244239c2b099f84500d18446ba04482e2b45447a8c8d9d93164be4
5
5
  SHA512:
6
- metadata.gz: 25dea9d82d42029ddb9f7cf5f0770005bf69c265531bcac22e89f843381abc29aa40b533ff00920c84a0ecdde2d889b423b16c1585f349e2d1c81abfdf877bdf
7
- data.tar.gz: 348f6928dad9f049376b7971924014cf24d458c36159978bdd9996cd7b33da151b49426765361586b46c5a3a410820a57f14854854c349b42e8c79b8a07c4048
6
+ metadata.gz: 31f0a5953dbbb9526c33d9710528cd1410cf120ce8b036d9d9cbcb3983b62cab77d0adaaf641b9dd3e342e6096eb85a3427353ea58396bdad76593bbcf392161
7
+ data.tar.gz: 692cb0d0042334e2e92847dc5aee81e2ecb090329b6763adf4d757da205ab61df88e24b3c54230d28285c1cae9007a9ca1c554769ed2d02452fba8afd623d2eb
@@ -2,7 +2,7 @@ name: CI
2
2
 
3
3
  on:
4
4
  push:
5
- branches: [master]
5
+ branches: [main]
6
6
  pull_request: {}
7
7
 
8
8
  concurrency:
@@ -33,6 +33,7 @@ jobs:
33
33
  - "3.1"
34
34
  - "3.2"
35
35
  - "3.3"
36
+ - "3.4"
36
37
  gemfile:
37
38
  - Gemfile
38
39
  - gemfiles/rails_5_2.gemfile
@@ -42,6 +43,8 @@ jobs:
42
43
  - gemfiles/rails_6_graphiti_rails.gemfile
43
44
  - gemfiles/rails_7_graphiti_rails.gemfile
44
45
  - gemfiles/rails_7_1_graphiti_rails.gemfile
46
+ - gemfiles/rails_7_2_graphiti_rails.gemfile
47
+ - gemfiles/rails_8_0_graphiti_rails.gemfile
45
48
  appraisal:
46
49
  - true
47
50
  - false
@@ -49,6 +52,12 @@ jobs:
49
52
  - ruby: ruby-head
50
53
  gemfile: gemfiles/rails_7_1.gemfile
51
54
  appraisal: true
55
+ - ruby: ruby-head
56
+ gemfile: gemfiles/rails_7_2_graphiti_rails.gemfile
57
+ appraisal: true
58
+ - ruby: ruby-head
59
+ gemfile: gemfiles/rails_8_0_graphiti_rails.gemfile
60
+ appraisal: true
52
61
  - ruby: ruby-head
53
62
  gemfile: Gemfile
54
63
  appraisal: false
@@ -70,7 +79,46 @@ jobs:
70
79
  appraisal: false
71
80
  - gemfile: gemfiles/rails_7_1_graphiti_rails.gemfile
72
81
  appraisal: false
73
- # Rails 5 can't run on Ruby 3
82
+ - gemfile: gemfiles/rails_7_2_graphiti_rails.gemfile
83
+ appraisal: false
84
+ - gemfile: gemfiles/rails_8_0_graphiti_rails.gemfile
85
+ appraisal: false
86
+
87
+ # Rails 8 needs ruby 3.2 +
88
+ - gemfile: gemfiles/rails_8_0_graphiti_rails.gemfile
89
+ ruby: 2.7
90
+ - gemfile: gemfiles/rails_8_0_graphiti_rails.gemfile
91
+ ruby: 3.0
92
+ - gemfile: gemfiles/rails_8_0_graphiti_rails.gemfile
93
+ ruby: 3.1
94
+
95
+ # Rails 7.2 needs ruby 3.1 +
96
+ - gemfile: gemfiles/rails_7_2_graphiti_rails.gemfile
97
+ ruby: 2.7
98
+ - gemfile: gemfiles/rails_7_2_graphiti_rails.gemfile
99
+ ruby: 3.0
100
+
101
+ # Rails 7.1 needs ruby 3.0 +
102
+ - gemfile: gemfiles/rails_7_1_graphiti_rails.gemfile
103
+ ruby: 2.7
104
+ - gemfile: gemfiles/rails_7_1_graphiti_rails.gemfile
105
+ ruby: 3.0
106
+
107
+ # Rails 7.0 needs ruby 3.1 +
108
+ - gemfile: gemfiles/rails_7_0_graphiti_rails.gemfile
109
+ ruby: 3.2
110
+ - gemfile: gemfiles/rails_7_0_graphiti_rails.gemfile
111
+ ruby: 3.3
112
+ - gemfile: gemfiles/rails_7_0_graphiti_rails.gemfile
113
+ ruby: 3.4
114
+
115
+ # Rails 6 needs < ruby 3.4
116
+ - gemfile: gemfiles/rails_6.gemfile
117
+ ruby: 3.4
118
+ - gemfile: gemfiles/rails_6_graphiti_rails.gemfile
119
+ ruby: 3.4
120
+
121
+ # Rails 5 can't run on Ruby 3
74
122
  - gemfile: gemfiles/rails_5_2.gemfile
75
123
  ruby: 3.0
76
124
  - gemfile: gemfiles/rails_5_2_graphiti_rails.gemfile
@@ -87,6 +135,10 @@ jobs:
87
135
  ruby: 3.3
88
136
  - gemfile: gemfiles/rails_5_2_graphiti_rails.gemfile
89
137
  ruby: 3.3
138
+ - gemfile: gemfiles/rails_5_2.gemfile
139
+ ruby: 3.4
140
+ - gemfile: gemfiles/rails_5_2_graphiti_rails.gemfile
141
+ ruby: 3.4
90
142
  continue-on-error: ${{ matrix.ruby == 'ruby-head' }}
91
143
  env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
92
144
  BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
@@ -104,7 +156,7 @@ jobs:
104
156
  publish:
105
157
  name: Release
106
158
  runs-on: ubuntu-latest
107
- if: github.ref == 'refs/heads/master'
159
+ if: github.ref == 'refs/heads/main'
108
160
  needs: [test]
109
161
  steps:
110
162
  - name: Dispatch Release
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  graphiti changelog
2
2
 
3
+ ## [1.8.2](https://github.com/graphiti-api/graphiti/compare/v1.8.1...v1.8.2) (2025-05-20)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * prevent context loss by always setting thread and fiber locals ([#497](https://github.com/graphiti-api/graphiti/issues/497)) ([5f45f76](https://github.com/graphiti-api/graphiti/commit/5f45f76f590a8a15e9ae3d47d0673c483da11e66))
9
+
10
+ ## [1.8.1](https://github.com/graphiti-api/graphiti/compare/v1.8.0...v1.8.1) (2025-03-17)
11
+
3
12
  # [1.8.0](https://github.com/graphiti-api/graphiti/compare/v1.7.9...v1.8.0) (2025-03-17)
4
13
 
5
14
 
data/README.md CHANGED
@@ -1,22 +1,228 @@
1
+ #### Graphiti
2
+
1
3
  [![CI](https://github.com/graphiti-api/graphiti/actions/workflows/ci.yml/badge.svg)](https://github.com/graphiti-api/graphiti/actions/workflows/ci.yml)
2
4
  [![Gem Version](https://badge.fury.io/rb/graphiti.svg)](https://badge.fury.io/rb/graphiti)
3
5
  [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
4
6
  [![semantic-release: angular](https://img.shields.io/badge/semantic--release-angular-e10079?logo=semantic-release)](https://github.com/semantic-release/semantic-release)
5
7
 
6
- <p align="center">
7
- <a href="https://www.graphiti.dev/guides">
8
- <img " src="https://user-images.githubusercontent.com/55264/54884141-c10ada00-4e43-11e9-866b-e3c01e33a7c7.png" />
9
- </a>
10
- </p>
11
-
12
- Stylish Graph APIs.
13
8
 
14
- [Website](https://www.graphiti.dev/guides/)
9
+ [![discord](https://img.shields.io/badge/community-discord-8A2BE2?logo=discord)](https://discord.gg/wgqkMBsSRV)
10
+ [![guides](https://img.shields.io/badge/guides-https://www.graphiti.dev-F565A5)](https://www.graphiti.dev)
11
+
12
+
13
+
14
+ <img align="right" src="https://user-images.githubusercontent.com/55264/54884141-c10ada00-4e43-11e9-866b-e3c01e33a7c7.png" alt="Graphiti logo" width="150px" />
15
+ Graphiti is a resource-oriented framework that sits on top of your models (usually ActiveRecord) and exposes them via a JSON:API-compliant interface. It abstracts common concerns like serialization, filtering, sorting, pagination, and sideloading relationships, so you can build powerful APIs with minimal boilerplate. By defining resources instead of controllers and serializers, Graphiti helps you keep your API logic organized, consistent, and easy to maintain.
16
+
17
+
18
+ #### Examples
19
+ Here's an example resource from the [example app](https://github.com/graphiti-api/employee_directory/) just to give you a taste of the possibilities.
20
+
21
+
22
+ ```ruby
23
+ class EmployeeResource < ApplicationResource
24
+ attribute :first_name, :string
25
+ attribute :last_name, :string
26
+ attribute :age, :integer
27
+ attribute :created_at, :datetime, writable: false
28
+ attribute :updated_at, :datetime, writable: false
29
+ attribute :title, :string, only: [:filterable, :sortable]
30
+
31
+ has_many :positions
32
+ has_many :tasks
33
+ many_to_many :teams
34
+ polymorphic_has_many :notes, as: :notable
35
+ has_one :current_position, resource: PositionResource do
36
+ params do |hash|
37
+ hash[:filter][:current] = true
38
+ end
39
+ end
40
+
41
+ filter :title, only: [:eq] do
42
+ eq do |scope, value|
43
+ scope.joins(:current_position).merge(Position.where(title: value))
44
+ end
45
+ end
46
+
47
+ sort :title do |scope, value|
48
+ scope.joins(:current_position).merge(Position.order(title: value))
49
+ end
50
+
51
+ sort :department_name, :string do |scope, value|
52
+ scope.joins(current_position: :department)
53
+ .merge(Department.order(name: value))
54
+ end
55
+ end
56
+ ```
57
+
58
+ A pretty boilerplate controller that just interfaces with the resource
59
+ ```ruby
60
+ class EmployeesController < ApplicationController
61
+ def index
62
+ employees = EmployeeResource.all(params)
63
+ respond_with(employees)
64
+ end
65
+
66
+ def show
67
+ employee = EmployeeResource.find(params)
68
+ respond_with(employee)
69
+ end
70
+
71
+ def create
72
+ employee = EmployeeResource.build(params)
73
+
74
+ if employee.save
75
+ render jsonapi: employee, status: 201
76
+ else
77
+ render jsonapi_errors: employee
78
+ end
79
+ end
80
+
81
+ def update
82
+ employee = EmployeeResource.find(params)
83
+
84
+ if employee.update_attributes
85
+ render jsonapi: employee
86
+ else
87
+ render jsonapi_errors: employee
88
+ end
89
+ end
90
+
91
+ def destroy
92
+ employee = EmployeeResource.find(params)
93
+
94
+ if employee.destroy
95
+ render jsonapi: { meta: {} }, status: 200
96
+ else
97
+ render jsonapi_errors: employee
98
+ end
99
+ end
100
+ end
101
+ ```
102
+
103
+ </details>
104
+
105
+
106
+ Now you can query your endpoints simply and powerfully, like:
107
+
108
+
109
+
110
+ Request:
111
+ ```http://localhost:3000/api/v1/employees?filter[title][eq]=Future Government Administrator&filter[age][lt]=40```
112
+
113
+ <details>
114
+ <summary>JSON-API response</summary>
115
+
116
+ ```json
117
+ {
118
+ "data": [
119
+ {
120
+ "id": "1",
121
+ "type": "employees",
122
+ "attributes": {
123
+ "first_name": "Quinn",
124
+ "last_name": "Homenick",
125
+ "age": 36,
126
+ "created_at": "2025-03-21T23:04:40+00:00",
127
+ "updated_at": "2025-03-21T23:04:40+00:00"
128
+ },
129
+ "relationships": {
130
+ "positions": {
131
+ "links": {
132
+ "related": "/api/v1/positions?filter[employee_id]=1"
133
+ },
134
+ "data": [
135
+ {
136
+ "type": "positions",
137
+ "id": "1"
138
+ },
139
+ {
140
+ "type": "positions",
141
+ "id": "2"
142
+ }
143
+ ]
144
+ },
145
+ "tasks": {
146
+ "links": {
147
+ "related": "/api/v1/tasks?filter[employee_id]=1"
148
+ }
149
+ },
150
+ "teams": {
151
+ "links": {
152
+ "related": "/api/v1/teams?filter[employee_id]=1"
153
+ }
154
+ },
155
+ "notes": {
156
+ "links": {
157
+ "related": "/api/v1/notes?filter[notable_id]=1&filter[notable_type][eql]=Employee"
158
+ }
159
+ },
160
+ "current_position": {
161
+ "links": {
162
+ "related": "/api/v1/positions?filter[current]=true&filter[employee_id]=1"
163
+ },
164
+ "data": {
165
+ "type": "positions",
166
+ "id": "1"
167
+ }
168
+ }
169
+ }
170
+ }
171
+ ],
172
+ "included": [
173
+ {
174
+ "id": "1",
175
+ "type": "positions",
176
+ "attributes": {
177
+ "title": "Future Government Administrator",
178
+ "active": true
179
+ },
180
+ "relationships": {
181
+ "employee": {
182
+ "links": {
183
+ "related": "/api/v1/employees/1"
184
+ }
185
+ },
186
+ "department": {
187
+ "links": {
188
+ "related": "/api/v1/departments/3"
189
+ }
190
+ }
191
+ }
192
+ },
193
+ {
194
+ "id": "2",
195
+ "type": "positions",
196
+ "attributes": {
197
+ "title": "Manufacturing Specialist",
198
+ "active": false
199
+ },
200
+ "relationships": {
201
+ "employee": {
202
+ "links": {
203
+ "related": "/api/v1/employees/1"
204
+ }
205
+ },
206
+ "department": {
207
+ "links": {
208
+ "related": "/api/v1/departments/2"
209
+ }
210
+ }
211
+ }
212
+ }
213
+ ],
214
+ "meta": {}
215
+ }
216
+ ```
217
+
218
+ </details>
219
+
220
+
221
+
222
+ [Graphiti Guides](https://www.graphiti.dev/guides/)
223
+
224
+ [Join the Discord](https://discord.gg/wgqkMBsSRV)
15
225
 
16
- [Join the Slack Channel](https://join.slack.com/t/graphiti-api/shared_invite/enQtMjkyMTA3MDgxNTQzLTU5MDI4MDllNTEzOTE1Nzk0ZGJlNTcxZDYzMGY2ZTczMDY2OWZhM2RmNTU0YWNiOWZhZDhkMmU4MzQ5NzIyNWM)
17
226
 
18
- Direct Contact: richmolj@gmail.com
19
227
 
20
- Supports Rails >= 4.1
21
228
 
22
- *Looking for JSONAPI-Suite? You're in the right place. Graphiti is the 1.0 version of JSONAPI Suite. For the deprecated Suite gem, go [here](https://github.com/jsonapi-suite/jsonapi_suite_deprecated)*
@@ -0,0 +1,19 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 7.2"
6
+ gem "rspec-rails"
7
+ gem "sqlite3", "~> 2.1"
8
+ gem "database_cleaner"
9
+ gem "graphiti-rails", "~> 0.4.0"
10
+
11
+ group :test do
12
+ gem "pry"
13
+ gem "pry-byebug", platform: [:mri]
14
+ gem "appraisal"
15
+ gem "guard"
16
+ gem "guard-rspec"
17
+ end
18
+
19
+ gemspec path: "../"
@@ -0,0 +1,19 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 8.0"
6
+ gem "rspec-rails"
7
+ gem "sqlite3", "~> 2.1"
8
+ gem "database_cleaner"
9
+ gem "graphiti-rails", "~> 0.4.0"
10
+
11
+ group :test do
12
+ gem "pry"
13
+ gem "pry-byebug", platform: [:mri]
14
+ gem "appraisal"
15
+ gem "guard"
16
+ gem "guard-rspec"
17
+ end
18
+
19
+ gemspec path: "../"
@@ -140,7 +140,7 @@ module Graphiti
140
140
  end
141
141
  fiber_storage =
142
142
  if Fiber.current.respond_to?(:storage)
143
- Fiber.current&.storage&.keys&.each_with_object({}) do |key, memo|
143
+ Fiber.current.storage&.keys&.each_with_object({}) do |key, memo|
144
144
  memo[key] = Fiber[key]
145
145
  end
146
146
  end
@@ -149,27 +149,46 @@ module Graphiti
149
149
  self.class.global_thread_pool_executor, Thread.current.object_id, thread_storage, fiber_storage, *args
150
150
  ) do |thread_id, thread_storage, fiber_storage, *args|
151
151
  wrap_in_rails_executor do
152
- execution_context_changed = thread_id != Thread.current.object_id
153
- if execution_context_changed
154
- thread_storage&.keys&.each_with_object(Thread.current) do |key, thread_current|
155
- thread_current[key] = thread_storage[key]
156
- end
157
- fiber_storage&.keys&.each_with_object(Fiber) do |key, fiber_current|
158
- fiber_current[key] = fiber_storage[key]
152
+ with_thread_locals(thread_storage) do
153
+ with_fiber_locals(fiber_storage) do
154
+ Graphiti.broadcast(:global_thread_pool_task_run, self.class.global_thread_pool_stats) do
155
+ yield(*args)
156
+ end
159
157
  end
160
158
  end
159
+ end
160
+ end
161
+ end
161
162
 
162
- result = Graphiti.broadcast(:global_thread_pool_task_run, self.class.global_thread_pool_stats) do
163
- yield(*args)
164
- end
163
+ def with_thread_locals(thread_locals)
164
+ new_thread_locals = []
165
+ thread_locals.each do |key, value|
166
+ if !Thread.current[key]
167
+ new_thread_locals << key
168
+ end
169
+ Thread.current[key] = value
170
+ end
171
+ yield
172
+ ensure
173
+ new_thread_locals.each do |key|
174
+ Thread.current[key] = nil
175
+ end
176
+ end
165
177
 
166
- if execution_context_changed
167
- thread_storage&.keys&.each { |key| Thread.current[key] = nil }
168
- fiber_storage&.keys&.each { |key| Fiber[key] = nil }
169
- end
178
+ def with_fiber_locals(fiber_locals)
179
+ return yield unless fiber_locals
170
180
 
171
- result
181
+ new_fiber_locals = []
182
+ fiber_locals.each do |key, value|
183
+ if !Fiber[key]
184
+ new_fiber_locals << key
172
185
  end
186
+ Fiber[key] = value
187
+ end
188
+ yield
189
+ ensure
190
+ new_fiber_locals&.each do |key|
191
+ Fiber[key] = nil
173
192
  end
174
193
  end
175
194
 
@@ -1,3 +1,3 @@
1
1
  module Graphiti
2
- VERSION = "1.8.0"
2
+ VERSION = "1.8.2"
3
3
  end
data/package.json CHANGED
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "release": {
23
23
  "branches": [
24
- "master",
24
+ "main",
25
25
  {
26
26
  "name": "beta",
27
27
  "prerelease": true
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphiti
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.0
4
+ version: 1.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lee Richmond
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-03-17 00:00:00.000000000 Z
11
+ date: 2025-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jsonapi-serializable
@@ -261,7 +261,9 @@ files:
261
261
  - gemfiles/rails_7.gemfile
262
262
  - gemfiles/rails_7_1.gemfile
263
263
  - gemfiles/rails_7_1_graphiti_rails.gemfile
264
+ - gemfiles/rails_7_2_graphiti_rails.gemfile
264
265
  - gemfiles/rails_7_graphiti_rails.gemfile
266
+ - gemfiles/rails_8_0_graphiti_rails.gemfile
265
267
  - graphiti.gemspec
266
268
  - lib/graphiti.rb
267
269
  - lib/graphiti/adapters/abstract.rb