jbuilder-schema 2.0.4 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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