jbuilder-schema 2.0.4 → 2.2.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: 2aea259ba4965ec62874b56ab4267ddc2f4cd2967babe536726397c53599c922
4
- data.tar.gz: 71c9f200fb4cc481055ec0790e514b9a46108a6ae4cb0c0aad1ed09eba5f384e
3
+ metadata.gz: 220162d597f3e157552d3ca783f9b4aba191f21ce2fb69172f24608d1439e840
4
+ data.tar.gz: 5ffd2a9d013ab15ff47d3d98dc4bcad5f79c735892f6733286d29032ee5d08f8
5
5
  SHA512:
6
- metadata.gz: d0ac95a76f5630daad73debfd74f6056dd3e6b4f30e4075779a41306585d211c918d16140de6f32b01a60db3303ac21e1ccdf8c52b9ea4292e32c38a134e1e21
7
- data.tar.gz: d88181a3d13d3748cfca995bbcc9de741bb9d5ecf98bd34af452b499aaeae2c0a6c44c5fc57ea88ee3892a2b6c2579f65a3310f1b59a83805067422ea20956c3
6
+ metadata.gz: 86cedff5f98aa84fe8399361058ecff9cb29cbfe63f3b2b2d9894b238b87348692b7354effbef595e83e9b86c01ceeac7f0dd2499c3d20b66e6874653cfcec4f
7
+ data.tar.gz: a40c484ba860d6f50669c83535deb0047c908f47c82bb59f757752d4ec6e1d30f1872bcfa9cb4636cd8df6de6a5a73d6ac290397c9ca9c28c26df70a17d34725
data/Gemfile.lock CHANGED
@@ -1,95 +1,98 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- jbuilder-schema (2.0.4)
4
+ jbuilder-schema (2.2.0)
5
5
  jbuilder
6
6
  rails (>= 5.0.0)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- actioncable (7.0.4)
12
- actionpack (= 7.0.4)
13
- activesupport (= 7.0.4)
11
+ actioncable (7.0.4.3)
12
+ actionpack (= 7.0.4.3)
13
+ activesupport (= 7.0.4.3)
14
14
  nio4r (~> 2.0)
15
15
  websocket-driver (>= 0.6.1)
16
- actionmailbox (7.0.4)
17
- actionpack (= 7.0.4)
18
- activejob (= 7.0.4)
19
- activerecord (= 7.0.4)
20
- activestorage (= 7.0.4)
21
- activesupport (= 7.0.4)
16
+ actionmailbox (7.0.4.3)
17
+ actionpack (= 7.0.4.3)
18
+ activejob (= 7.0.4.3)
19
+ activerecord (= 7.0.4.3)
20
+ activestorage (= 7.0.4.3)
21
+ activesupport (= 7.0.4.3)
22
22
  mail (>= 2.7.1)
23
23
  net-imap
24
24
  net-pop
25
25
  net-smtp
26
- actionmailer (7.0.4)
27
- actionpack (= 7.0.4)
28
- actionview (= 7.0.4)
29
- activejob (= 7.0.4)
30
- activesupport (= 7.0.4)
26
+ actionmailer (7.0.4.3)
27
+ actionpack (= 7.0.4.3)
28
+ actionview (= 7.0.4.3)
29
+ activejob (= 7.0.4.3)
30
+ activesupport (= 7.0.4.3)
31
31
  mail (~> 2.5, >= 2.5.4)
32
32
  net-imap
33
33
  net-pop
34
34
  net-smtp
35
35
  rails-dom-testing (~> 2.0)
36
- actionpack (7.0.4)
37
- actionview (= 7.0.4)
38
- activesupport (= 7.0.4)
36
+ actionpack (7.0.4.3)
37
+ actionview (= 7.0.4.3)
38
+ activesupport (= 7.0.4.3)
39
39
  rack (~> 2.0, >= 2.2.0)
40
40
  rack-test (>= 0.6.3)
41
41
  rails-dom-testing (~> 2.0)
42
42
  rails-html-sanitizer (~> 1.0, >= 1.2.0)
43
- actiontext (7.0.4)
44
- actionpack (= 7.0.4)
45
- activerecord (= 7.0.4)
46
- activestorage (= 7.0.4)
47
- activesupport (= 7.0.4)
43
+ actiontext (7.0.4.3)
44
+ actionpack (= 7.0.4.3)
45
+ activerecord (= 7.0.4.3)
46
+ activestorage (= 7.0.4.3)
47
+ activesupport (= 7.0.4.3)
48
48
  globalid (>= 0.6.0)
49
49
  nokogiri (>= 1.8.5)
50
- actionview (7.0.4)
51
- activesupport (= 7.0.4)
50
+ actionview (7.0.4.3)
51
+ activesupport (= 7.0.4.3)
52
52
  builder (~> 3.1)
53
53
  erubi (~> 1.4)
54
54
  rails-dom-testing (~> 2.0)
55
55
  rails-html-sanitizer (~> 1.1, >= 1.2.0)
56
- activejob (7.0.4)
57
- activesupport (= 7.0.4)
56
+ activejob (7.0.4.3)
57
+ activesupport (= 7.0.4.3)
58
58
  globalid (>= 0.3.6)
59
- activemodel (7.0.4)
60
- activesupport (= 7.0.4)
61
- activerecord (7.0.4)
62
- activemodel (= 7.0.4)
63
- activesupport (= 7.0.4)
64
- activestorage (7.0.4)
65
- actionpack (= 7.0.4)
66
- activejob (= 7.0.4)
67
- activerecord (= 7.0.4)
68
- activesupport (= 7.0.4)
59
+ activemodel (7.0.4.3)
60
+ activesupport (= 7.0.4.3)
61
+ activerecord (7.0.4.3)
62
+ activemodel (= 7.0.4.3)
63
+ activesupport (= 7.0.4.3)
64
+ activestorage (7.0.4.3)
65
+ actionpack (= 7.0.4.3)
66
+ activejob (= 7.0.4.3)
67
+ activerecord (= 7.0.4.3)
68
+ activesupport (= 7.0.4.3)
69
69
  marcel (~> 1.0)
70
70
  mini_mime (>= 1.1.0)
71
- activesupport (7.0.4)
71
+ activesupport (7.0.4.3)
72
72
  concurrent-ruby (~> 1.0, >= 1.0.2)
73
73
  i18n (>= 1.6, < 2)
74
74
  minitest (>= 5.1)
75
75
  tzinfo (~> 2.0)
76
76
  ast (2.4.2)
77
77
  builder (3.2.4)
78
- concurrent-ruby (1.1.10)
78
+ concurrent-ruby (1.2.2)
79
79
  crass (1.0.6)
80
- erubi (1.11.0)
81
- globalid (1.0.0)
80
+ date (3.3.3)
81
+ erubi (1.12.0)
82
+ globalid (1.1.0)
82
83
  activesupport (>= 5.0)
83
- i18n (1.12.0)
84
+ i18n (1.13.0)
84
85
  concurrent-ruby (~> 1.0)
85
86
  jbuilder (2.11.5)
86
87
  actionview (>= 5.0.0)
87
88
  activesupport (>= 5.0.0)
88
- json (2.6.2)
89
- loofah (2.19.0)
89
+ json (2.6.3)
90
+ language_server-protocol (3.17.0.3)
91
+ lint_roller (1.0.0)
92
+ loofah (2.20.0)
90
93
  crass (~> 1.0.2)
91
94
  nokogiri (>= 1.5.9)
92
- mail (2.8.0)
95
+ mail (2.8.1)
93
96
  mini_mime (>= 0.1.1)
94
97
  net-imap
95
98
  net-pop
@@ -97,88 +100,99 @@ GEM
97
100
  marcel (1.0.2)
98
101
  method_source (1.0.0)
99
102
  mini_mime (1.1.2)
100
- mini_portile2 (2.8.0)
101
- minitest (5.16.3)
102
- mocha (1.14.0)
103
- net-imap (0.3.1)
103
+ mini_portile2 (2.8.2)
104
+ minitest (5.18.0)
105
+ mocha (2.0.2)
106
+ ruby2_keywords (>= 0.0.5)
107
+ net-imap (0.3.4)
108
+ date
104
109
  net-protocol
105
110
  net-pop (0.1.2)
106
111
  net-protocol
107
- net-protocol (0.2.0)
112
+ net-protocol (0.2.1)
108
113
  timeout
109
114
  net-smtp (0.3.3)
110
115
  net-protocol
111
- nio4r (2.5.8)
112
- nokogiri (1.13.9)
116
+ nio4r (2.5.9)
117
+ nokogiri (1.14.3)
113
118
  mini_portile2 (~> 2.8.0)
114
119
  racc (~> 1.4)
115
- parallel (1.22.1)
116
- parser (3.1.2.0)
120
+ parallel (1.23.0)
121
+ parser (3.2.2.1)
117
122
  ast (~> 2.4.1)
118
- racc (1.6.1)
119
- rack (2.2.4)
120
- rack-test (2.0.2)
123
+ racc (1.6.2)
124
+ rack (2.2.7)
125
+ rack-test (2.1.0)
121
126
  rack (>= 1.3)
122
- rails (7.0.4)
123
- actioncable (= 7.0.4)
124
- actionmailbox (= 7.0.4)
125
- actionmailer (= 7.0.4)
126
- actionpack (= 7.0.4)
127
- actiontext (= 7.0.4)
128
- actionview (= 7.0.4)
129
- activejob (= 7.0.4)
130
- activemodel (= 7.0.4)
131
- activerecord (= 7.0.4)
132
- activestorage (= 7.0.4)
133
- activesupport (= 7.0.4)
127
+ rails (7.0.4.3)
128
+ actioncable (= 7.0.4.3)
129
+ actionmailbox (= 7.0.4.3)
130
+ actionmailer (= 7.0.4.3)
131
+ actionpack (= 7.0.4.3)
132
+ actiontext (= 7.0.4.3)
133
+ actionview (= 7.0.4.3)
134
+ activejob (= 7.0.4.3)
135
+ activemodel (= 7.0.4.3)
136
+ activerecord (= 7.0.4.3)
137
+ activestorage (= 7.0.4.3)
138
+ activesupport (= 7.0.4.3)
134
139
  bundler (>= 1.15.0)
135
- railties (= 7.0.4)
140
+ railties (= 7.0.4.3)
136
141
  rails-dom-testing (2.0.3)
137
142
  activesupport (>= 4.2.0)
138
143
  nokogiri (>= 1.6)
139
- rails-html-sanitizer (1.4.3)
140
- loofah (~> 2.3)
141
- railties (7.0.4)
142
- actionpack (= 7.0.4)
143
- activesupport (= 7.0.4)
144
+ rails-html-sanitizer (1.5.0)
145
+ loofah (~> 2.19, >= 2.19.1)
146
+ railties (7.0.4.3)
147
+ actionpack (= 7.0.4.3)
148
+ activesupport (= 7.0.4.3)
144
149
  method_source
145
150
  rake (>= 12.2)
146
151
  thor (~> 1.0)
147
152
  zeitwerk (~> 2.5)
148
153
  rainbow (3.1.1)
149
154
  rake (13.0.6)
150
- regexp_parser (2.5.0)
155
+ regexp_parser (2.8.0)
151
156
  rexml (3.2.5)
152
- rubocop (1.32.0)
157
+ rubocop (1.50.2)
153
158
  json (~> 2.3)
154
159
  parallel (~> 1.10)
155
- parser (>= 3.1.0.0)
160
+ parser (>= 3.2.0.0)
156
161
  rainbow (>= 2.2.2, < 4.0)
157
162
  regexp_parser (>= 1.8, < 3.0)
158
163
  rexml (>= 3.2.5, < 4.0)
159
- rubocop-ast (>= 1.19.1, < 2.0)
164
+ rubocop-ast (>= 1.28.0, < 2.0)
160
165
  ruby-progressbar (~> 1.7)
161
- unicode-display_width (>= 1.4.0, < 3.0)
162
- rubocop-ast (1.19.1)
163
- parser (>= 3.1.1.0)
164
- rubocop-performance (1.14.3)
166
+ unicode-display_width (>= 2.4.0, < 3.0)
167
+ rubocop-ast (1.28.1)
168
+ parser (>= 3.2.1.0)
169
+ rubocop-performance (1.16.0)
165
170
  rubocop (>= 1.7.0, < 2.0)
166
171
  rubocop-ast (>= 0.4.0)
167
- ruby-progressbar (1.11.0)
168
- sqlite3 (1.5.3)
172
+ ruby-progressbar (1.13.0)
173
+ ruby2_keywords (0.0.5)
174
+ sqlite3 (1.6.2)
169
175
  mini_portile2 (~> 2.8.0)
170
- standard (1.14.0)
171
- rubocop (= 1.32.0)
172
- rubocop-performance (= 1.14.3)
176
+ standard (1.28.2)
177
+ language_server-protocol (~> 3.17.0.2)
178
+ lint_roller (~> 1.0)
179
+ rubocop (~> 1.50.2)
180
+ standard-custom (~> 1.0.0)
181
+ standard-performance (~> 1.0.1)
182
+ standard-custom (1.0.0)
183
+ lint_roller (~> 1.0)
184
+ standard-performance (1.0.1)
185
+ lint_roller (~> 1.0)
186
+ rubocop-performance (~> 1.16.0)
173
187
  thor (1.2.1)
174
- timeout (0.3.1)
175
- tzinfo (2.0.5)
188
+ timeout (0.3.2)
189
+ tzinfo (2.0.6)
176
190
  concurrent-ruby (~> 1.0)
177
- unicode-display_width (2.2.0)
191
+ unicode-display_width (2.4.2)
178
192
  websocket-driver (0.7.5)
179
193
  websocket-extensions (>= 0.1.0)
180
194
  websocket-extensions (0.1.5)
181
- zeitwerk (2.6.6)
195
+ zeitwerk (2.6.8)
182
196
 
183
197
  PLATFORMS
184
198
  ruby
@@ -191,4 +205,4 @@ DEPENDENCIES
191
205
  standard (~> 1.3)
192
206
 
193
207
  BUNDLED WITH
194
- 2.3.16
208
+ 2.4.10
data/README.md CHANGED
@@ -2,9 +2,6 @@
2
2
 
3
3
  Generate JSON Schema compatible with OpenAPI 3 specs from Jbuilder files
4
4
 
5
- [![Tests](https://github.com/bullet-train-co/jbuilder-schema/actions/workflows/tests.yml/badge.svg)](https://github.com/bullet-train-co/jbuilder-schema/actions)
6
- [![Standard](https://github.com/bullet-train-co/jbuilder-schema/actions/workflows/standard.yml/badge.svg)](https://github.com/bullet-train-co/jbuilder-schema/actions)
7
-
8
5
  ## Installation
9
6
 
10
7
  In your Gemfile, put `gem "jbuilder-schema"` after Jbuilder:
@@ -3,6 +3,7 @@ require_relative "template"
3
3
 
4
4
  class Jbuilder::Schema::Renderer
5
5
  @@view_renderer = ActionView::Base.with_empty_template_cache
6
+ @@view_renderer.prefix_partial_path_with_controller_namespace = false
6
7
 
7
8
  def initialize(paths, default_locals = nil)
8
9
  @view_renderer = @@view_renderer.with_view_paths(paths)
@@ -18,19 +19,32 @@ class Jbuilder::Schema::Renderer
18
19
  end
19
20
 
20
21
  def render(object = nil, title: nil, description: nil, assigns: nil, **options)
21
- if object
22
- partial_path = object.respond_to?(:to_partial_path_for_jbuilder_schema) ? object.to_partial_path_for_jbuilder_schema : object.to_partial_path
23
- options.merge! partial: partial_path, object: object
22
+ @view_renderer.assign assigns if assigns
23
+
24
+ partial_path = %i[to_partial_path_for_jbuilder_schema to_partial_path].map { object.public_send(_1) if object.respond_to?(_1) }.compact.first
25
+ if partial_path
26
+ options[:partial] = partial_path
27
+ options[:object] = object
28
+ end
29
+
30
+ json = if partial_path
31
+ original_render(options.dup, options.dup)
32
+ else
33
+ original_render(object || options.dup, options.dup)
24
34
  end
25
35
 
26
36
  options[:locals] ||= {}
27
37
  options[:locals].merge! @default_locals if @default_locals
28
- options[:locals][:__jbuilder_schema_options] = { model: object&.class, title: title, description: description }
38
+ options[:locals][:__jbuilder_schema_options] = {json: json, object: object, title: title, description: description}
29
39
 
30
- @view_renderer.assign assigns if assigns
31
40
  @view_renderer.render(options)
32
41
  end
33
42
 
43
+ # Thin wrapper around the regular Jbuilder JSON output render, which also parses it into a hash.
44
+ def original_render(options = {}, locals = {})
45
+ JSON.parse @view_renderer.render(options, locals)
46
+ end
47
+
34
48
  private
35
49
 
36
50
  def normalize(schema)
@@ -5,9 +5,6 @@ require "active_support/inflections"
5
5
 
6
6
  class Jbuilder::Schema
7
7
  class Template < ::JbuilderTemplate
8
- attr_reader :attributes, :type
9
- attr_reader :model_scope
10
-
11
8
  class Handler < ::JbuilderHandler
12
9
  def self.call(template, source = nil)
13
10
  super.sub("JbuilderTemplate.new(self", "Jbuilder::Schema::Template.build(self, local_assigns")
@@ -26,34 +23,34 @@ class Jbuilder::Schema
26
23
  end
27
24
  end
28
25
 
29
- ModelScope = ::Struct.new(:model, :title, :description, keyword_init: true) do
30
- def initialize(**)
31
- super
32
- @scope = model&.name&.underscore&.pluralize
26
+ class Configuration < ::Struct.new(:object, :title, :description, keyword_init: true)
27
+ def self.build(object: nil, object_title: nil, object_description: nil, **)
28
+ new(object: object, title: object_title, description: object_description)
33
29
  end
34
30
 
35
- def i18n_title
36
- title || ::I18n.t(::Jbuilder::Schema.title_name, scope: @scope)
31
+ def title
32
+ super || translate(Jbuilder::Schema.title_name)
37
33
  end
38
34
 
39
- def i18n_description
40
- description || ::I18n.t(::Jbuilder::Schema.description_name, scope: @scope)
35
+ def description
36
+ super || translate(Jbuilder::Schema.description_name)
41
37
  end
42
38
 
43
39
  def translate_field(key)
44
- ::I18n.t("fields.#{key}.#{::Jbuilder::Schema.description_name}", scope: @scope)
40
+ translate("fields.#{key}.#{Jbuilder::Schema.description_name}")
45
41
  end
46
- end
47
42
 
48
- def initialize(context, **options)
49
- @type = :object
50
- @inline_array = false
51
- @collection = false
43
+ private
52
44
 
53
- @model_scope = ModelScope.new(**options)
45
+ def translate(key)
46
+ I18n.t(key, scope: @scope ||= object&.class&.name&.underscore&.pluralize)
47
+ end
48
+ end
54
49
 
50
+ def initialize(context, json: nil, **options)
51
+ @json = json
52
+ @configuration = Configuration.new(**options)
55
53
  super(context)
56
-
57
54
  @ignore_nil = false
58
55
  end
59
56
 
@@ -62,176 +59,116 @@ class Jbuilder::Schema
62
59
  end
63
60
 
64
61
  def schema!
65
- {type: type}.merge(type == :object ? _object(**attributes.merge) : attributes)
62
+ if ([@attributes] + @attributes.each_value.grep(::Hash)).any? { _1[:type] == :array && _1.key?(:items) }
63
+ @attributes
64
+ else
65
+ _object(@attributes, _required!(@attributes.keys))
66
+ end.merge(example: @json).compact
66
67
  end
67
68
 
68
- def set!(key, value = BLANK, *args, schema: {}, **options, &block)
69
- result = if block
70
- if !_blank?(value)
71
- # OBJECTS ARRAY:
72
- # json.comments @article.comments { |comment| ... }
73
- # { "comments": [ { ... }, { ... } ] }
74
- _scope { array! value, &block }
75
- else
76
- # BLOCK:
77
- # json.comments { ... }
78
- # { "comments": ... }
79
- @inline_array = true
80
-
81
- _with_model_scope(**schema) do
82
- _merge_block(key) { yield self }
83
- end
84
- end
85
- elsif args.empty?
86
- if ::Jbuilder === value
87
- # ATTRIBUTE1:
88
- # json.age 32
89
- # json.person another_jbuilder
90
- # { "age": 32, "person": { ... }
91
- _schema(key, _format_keys(value.attributes!), **schema)
92
- elsif _is_collection_array?(value)
93
- # ATTRIBUTE2:
94
- _scope { array! value }
95
- # json.articles @articles
96
- else
97
- # json.age 32
98
- # { "age": 32 }
99
- _schema(key, _format_keys(value), **schema)
100
- end
101
- elsif _is_collection?(value)
102
- # COLLECTION:
103
- # json.comments @article.comments, :content, :created_at
104
- # { "comments": [ { "content": "hello", "created_at": "..." }, { "content": "world", "created_at": "..." } ] }
105
- @inline_array = true
106
- @collection = true
107
-
108
- _scope { array! value, *args }
109
- else
110
- # EXTRACT!:
111
- # json.author @article.creator, :name, :email_address
112
- # { "author": { "name": "David", "email_address": "david@loudthinking.com" } }
113
- _with_model_scope(**schema) do
114
- _merge_block(key) { extract! value, *args, schema: schema }
115
- end
116
- end
69
+ def set!(key, value = BLANK, *args, schema: nil, **options, &block)
70
+ old_configuration, @configuration = @configuration, Configuration.build(**schema) if schema&.dig(:object)
117
71
 
118
- result = _set_description key, result if model_scope.model
119
- _set_value key, result
120
- end
72
+ _with_schema_overrides(key => schema) do
73
+ keys = args.presence || _extract_possible_keys(value)
121
74
 
122
- def extract!(object, *attributes, schema: {})
123
- if ::Hash === object
124
- _extract_hash_values(object, attributes, schema: schema)
125
- else
126
- _extract_method_values(object, attributes, schema: schema)
75
+ # Detect `json.articles user.articles` to override Jbuilder's logic, which wouldn't hit `array!` and set a `type: :array, items: {"$ref": "#/components/schemas/article"}` ref.
76
+ if block.nil? && keys.blank? && _is_collection?(value) && (value.empty? || value.all? { _is_active_model?(_1) })
77
+ _set_value(key, _scope { _set_ref(key.to_s.singularize, array: true) })
78
+ else
79
+ super(key, value, *keys, **options, &block)
80
+ end
127
81
  end
82
+ ensure
83
+ @configuration = old_configuration if old_configuration
128
84
  end
85
+ alias_method :method_missing, :set! # TODO: Remove once Jbuilder passes keyword arguments along to `set!` in its `method_missing`.
129
86
 
130
- def array!(collection = [], *args, schema: {}, **options, &block)
87
+ def array!(collection = [], *args, schema: nil, **options, &block)
131
88
  if _partial_options?(options)
132
- @collection = true
133
- _set_ref(options[:partial].split("/").last)
89
+ partial!(collection: collection, **options)
134
90
  else
135
- array = _make_array(collection, *args, schema: schema, &block)
136
-
137
- if @inline_array
138
- @attributes = {}
139
- _set_value(:type, :array)
140
- _set_value(:items, array)
141
- elsif _is_collection_array?(array)
142
- @attributes = {}
143
- @inline_array = true
144
- @collection = true
145
- array! array, *array.first&.attribute_names(&:to_sym)
146
- else
147
- @type = :array
148
- @attributes = {}
149
- _set_value(:items, array)
91
+ _with_schema_overrides(schema) do
92
+ _attributes.merge! type: :array, items: _scope { super(collection, *args, &block) }
150
93
  end
151
94
  end
152
95
  end
153
96
 
154
- def partial!(*args)
155
- if args.one? && _is_active_model?(args.first)
97
+ def extract!(object, *attributes, schema: nil)
98
+ _with_schema_overrides(schema) { super(object, *attributes) }
99
+ end
100
+
101
+ def partial!(model = nil, *args, partial: nil, collection: nil, **options)
102
+ if args.none? && _is_active_model?(model)
156
103
  # TODO: Find where it is being used
157
- _render_active_model_partial args.first
158
- elsif args.first.is_a?(::Hash)
159
- _set_ref(args.first[:partial].split("/").last)
104
+ _render_active_model_partial model
160
105
  else
161
- @collection = true if args[1].key?(:collection)
162
- _set_ref(args.first&.split("/")&.last)
106
+ _set_ref(partial || model, array: collection&.any?)
163
107
  end
164
108
  end
165
109
 
166
110
  def merge!(object)
167
- hash_or_array = ::Jbuilder === object ? object.attributes! : object
168
- hash_or_array = _format_keys(hash_or_array)
169
- if hash_or_array.is_a?(::Hash)
170
- hash_or_array = hash_or_array.each_with_object({}) do |(key, value), a|
171
- result = _schema(key, value)
172
- result = _set_description(key, result) if model_scope.model
173
- a[key] = result
174
- end
175
- end
176
- @attributes = _merge_values(@attributes, hash_or_array)
111
+ object = object.to_h { [_1, _schema(_1, _2)] } if object.is_a?(::Hash)
112
+ super
177
113
  end
178
114
 
179
115
  def cache!(key = nil, **options)
180
116
  yield # TODO: Our schema generation breaks Jbuilder's fragment caching.
181
117
  end
182
118
 
183
- def method_missing(*args, **options, &block) # standard:disable Style/MissingRespondToMissing
184
- # TODO: Remove once Jbuilder passes keyword arguments along to `set!` in its `method_missing`.
185
- set!(*args, **options, &block)
186
- end
187
-
188
119
  private
189
120
 
190
- def _with_model_scope(object: nil, object_title: nil, object_description: nil, **)
191
- old_model_scope, @model_scope = @model_scope, ModelScope.new(model: object.class, title: object_title, description: object_description) if object
121
+ def _extract_possible_keys(value)
122
+ value.first.as_json.keys if _is_collection?(value) && _is_active_model?(value.first)
123
+ end
124
+
125
+ def _with_schema_overrides(overrides)
126
+ old_schema_overrides, @schema_overrides = @schema_overrides, overrides if overrides
192
127
  yield
193
128
  ensure
194
- @model_scope = old_model_scope if object
129
+ @schema_overrides = old_schema_overrides if overrides
195
130
  end
196
131
 
197
- def _object(**attributes)
132
+ def _object(attributes, required)
198
133
  {
199
134
  type: :object,
200
- title: model_scope.i18n_title,
201
- description: model_scope.i18n_description,
202
- required: _required!(attributes.keys),
203
- properties: attributes
135
+ title: @configuration.title,
136
+ description: @configuration.description,
137
+ required: required,
138
+ properties: _nullify_non_required_types(attributes, required)
204
139
  }
205
140
  end
206
141
 
142
+ def _nullify_non_required_types(attributes, required)
143
+ attributes.transform_values! { _1[:type] = [_1[:type], "null"] unless required.include?(attributes.key(_1)); _1 }
144
+ end
145
+
207
146
  def _set_description(key, value)
208
- unless value.key?(:description)
209
- description = model_scope.translate_field(key)
210
- value = {description: description}.merge! value
147
+ if !value.key?(:description) && @configuration.object
148
+ value[:description] = @configuration.translate_field(key)
211
149
  end
212
- value
213
150
  end
214
151
 
215
- def _set_ref(component)
216
- component_path = "#/#{::Jbuilder::Schema.components_path}/#{component}"
152
+ def _set_ref(part, array: false)
153
+ ref = {"$ref": "#/#{::Jbuilder::Schema.components_path}/#{part.split("/").last}"}
217
154
 
218
- if @inline_array
219
- if @collection
220
- _set_value(:type, :array)
221
- _set_value(:items, {:$ref => component_path})
222
- else
223
- _set_value(:type, :object)
224
- _set_value(:$ref, component_path)
225
- end
155
+ if array
156
+ _attributes.merge! type: :array, items: ref
226
157
  else
227
- @type = :array
228
- _set_value(:items, {:$ref => component_path})
158
+ _attributes.merge! type: :object, **ref
229
159
  end
230
160
  end
231
161
 
162
+ def _attributes
163
+ @attributes = {} if _blank?
164
+ @attributes
165
+ end
166
+
232
167
  FORMATS = {::DateTime => "date-time", ::ActiveSupport::TimeWithZone => "date-time", ::Date => "date", ::Time => "time"}
233
168
 
234
169
  def _schema(key, value, **options)
170
+ options = @schema_overrides&.dig(key).to_h if options.empty?
171
+
235
172
  unless options[:type]
236
173
  options[:type] = _primitive_type value
237
174
 
@@ -243,15 +180,16 @@ class Jbuilder::Schema
243
180
  format = FORMATS[value.class] and options[:format] ||= format
244
181
  end
245
182
 
246
- if (model = model_scope.model) && (defined_enum = model.try(:defined_enums)&.dig(key.to_s))
183
+ if (klass = @configuration.object&.class) && (defined_enum = klass.try(:defined_enums)&.dig(key.to_s))
247
184
  options[:enum] = defined_enum.keys
248
185
  end
249
186
 
187
+ _set_description key, options
250
188
  options
251
189
  end
252
190
 
253
- def _primitive_type(type)
254
- case type
191
+ def _primitive_type(value)
192
+ case value
255
193
  when ::Array then :array
256
194
  when ::Float, ::BigDecimal then :number
257
195
  when true, false then :boolean
@@ -261,47 +199,21 @@ class Jbuilder::Schema
261
199
  end
262
200
  end
263
201
 
264
- def _make_array(collection, *args, schema: {}, &block)
265
- if collection.nil?
266
- []
267
- elsif block
268
- _map_collection(collection, &block)
269
- elsif args.any?
270
- _map_collection(collection) { |element| extract! element, *args, schema: schema }
271
- else
272
- _format_keys(collection.to_a)
273
- end
274
- end
275
-
276
- def _is_collection_array?(object)
277
- object.is_a?(::Array) && object.all? { _is_active_model? _1 }
202
+ def _set_value(key, value)
203
+ value = _schema(key, value) unless value.is_a?(::Hash) && value.key?(:type)
204
+ _set_description(key, value)
205
+ super
278
206
  end
279
207
 
280
208
  def _required!(keys)
281
- presence_validated_attributes = model_scope.model.try(:validators).to_a.flat_map { _1.attributes if _1.is_a?(::ActiveRecord::Validations::PresenceValidator) }
282
- keys & [_key(:id), *presence_validated_attributes.map { _key _1 }]
209
+ presence_validated_attributes = @configuration.object&.class.try(:validators).to_a.flat_map { _1.attributes if _1.is_a?(::ActiveRecord::Validations::PresenceValidator) }
210
+ keys & [_key(:id), *presence_validated_attributes.flat_map { [_key(_1), _key("#{_1}_id")] }]
283
211
  end
284
212
 
285
213
  ###
286
214
  # Jbuilder methods
287
215
  ###
288
216
 
289
- def _extract_hash_values(object, attributes, schema:)
290
- attributes.each do |key|
291
- result = _schema(key, _format_keys(object.fetch(key)), **schema[key] || {})
292
- result = _set_description(key, result) if model_scope.model
293
- _set_value key, result
294
- end
295
- end
296
-
297
- def _extract_method_values(object, attributes, schema:)
298
- attributes.each do |key|
299
- result = _schema(key, _format_keys(object.public_send(key)), **schema[key] || {})
300
- result = _set_description(key, result) if model_scope.model
301
- _set_value key, result
302
- end
303
- end
304
-
305
217
  def _map_collection(collection)
306
218
  super.first
307
219
  end
@@ -311,20 +223,8 @@ class Jbuilder::Schema
311
223
  raise NullError.build(key) if current_value.nil?
312
224
 
313
225
  value = _scope { yield self }
314
- value = _object(**value) unless value.values_at("type", :type).any?(:array) || value.key?(:$ref) || value.key?("$ref")
226
+ value = _object(value, _required!(value.keys)) unless value[:type] == :array || value.key?(:$ref)
315
227
  _merge_values(current_value, value)
316
228
  end
317
229
  end
318
230
  end
319
-
320
- class Jbuilder
321
- module SkipFormatting
322
- SCHEMA_KEYS = %i[type items properties]
323
-
324
- def format(key)
325
- SCHEMA_KEYS.include?(key) ? key : super
326
- end
327
- end
328
-
329
- KeyFormatter.prepend SkipFormatting
330
- end
@@ -1,4 +1,4 @@
1
1
  # We can't use the standard `Jbuilder::Schema::VERSION =` because
2
2
  # `Jbuilder` isn't a regular module namespace, but a class …which also loads Active Support.
3
3
  # So we use trickery, and assign the proper version once `jbuilder/schema.rb` is loaded.
4
- JBUILDER_SCHEMA_VERSION = "2.0.4"
4
+ JBUILDER_SCHEMA_VERSION = "2.2.0"
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/module/delegation"
3
+ require "jbuilder"
4
4
  require "jbuilder/schema/version"
5
+ require "active_support/core_ext/module/delegation"
5
6
 
6
7
  class Jbuilder::Schema
7
- VERSION = "2.0.3" # TODO Fix this. It's throwing errors when including the Ruby gem in downstream projects.
8
+ VERSION = JBUILDER_SCHEMA_VERSION # See `jbuilder/schema/version.rb`
8
9
 
9
10
  module IgnoreSchemaMeta
10
11
  ::Jbuilder.prepend self
@@ -12,6 +13,18 @@ class Jbuilder::Schema
12
13
  def method_missing(*args, schema: nil, **options, &block) # standard:disable Style/MissingRespondToMissing
13
14
  super(*args, **options, &block)
14
15
  end
16
+
17
+ def set!(*args, schema: nil, **options, &block)
18
+ super(*args, **options, &block)
19
+ end
20
+
21
+ def array!(*args, schema: nil, **options, &block)
22
+ super(*args, **options, &block)
23
+ end
24
+
25
+ def extract!(*args, schema: nil, **options, &block)
26
+ super(*args, **options, &block)
27
+ end
15
28
  end
16
29
 
17
30
  singleton_class.alias_method :configure, :tap
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jbuilder-schema
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.4
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yuri Sidorov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-12-07 00:00:00.000000000 Z
11
+ date: 2023-05-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jbuilder
@@ -76,7 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
76
76
  - !ruby/object:Gem::Version
77
77
  version: '0'
78
78
  requirements: []
79
- rubygems_version: 3.3.16
79
+ rubygems_version: 3.4.10
80
80
  signing_key:
81
81
  specification_version: 4
82
82
  summary: Generate JSON Schema from Jbuilder files