jbuilder 2.7.0 → 2.11.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,19 +1,17 @@
1
1
  <% if namespaced? -%>
2
- require_dependency "<%= namespaced_file_path %>/application_controller"
2
+ require_dependency "<%= namespaced_path %>/application_controller"
3
3
 
4
4
  <% end -%>
5
5
  <% module_namespacing do -%>
6
6
  class <%= controller_class_name %>Controller < ApplicationController
7
- before_action :set_<%= singular_table_name %>, only: [:show, :edit, :update, :destroy]
7
+ before_action :set_<%= singular_table_name %>, only: %i[ show edit update destroy ]
8
8
 
9
- # GET <%= route_url %>
10
- # GET <%= route_url %>.json
9
+ # GET <%= route_url %> or <%= route_url %>.json
11
10
  def index
12
11
  @<%= plural_table_name %> = <%= orm_class.all(class_name) %>
13
12
  end
14
13
 
15
- # GET <%= route_url %>/1
16
- # GET <%= route_url %>/1.json
14
+ # GET <%= route_url %>/1 or <%= route_url %>/1.json
17
15
  def show
18
16
  end
19
17
 
@@ -26,42 +24,40 @@ class <%= controller_class_name %>Controller < ApplicationController
26
24
  def edit
27
25
  end
28
26
 
29
- # POST <%= route_url %>
30
- # POST <%= route_url %>.json
27
+ # POST <%= route_url %> or <%= route_url %>.json
31
28
  def create
32
29
  @<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}_params") %>
33
30
 
34
31
  respond_to do |format|
35
32
  if @<%= orm_instance.save %>
36
- format.html { redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully created.'" %> }
33
+ format.html { redirect_to <%= show_helper %>, notice: <%= %("#{human_name} was successfully created.") %> }
37
34
  format.json { render :show, status: :created, location: <%= "@#{singular_table_name}" %> }
38
35
  else
39
- format.html { render :new }
36
+ format.html { render :new, status: :unprocessable_entity }
40
37
  format.json { render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity }
41
38
  end
42
39
  end
43
40
  end
44
41
 
45
- # PATCH/PUT <%= route_url %>/1
46
- # PATCH/PUT <%= route_url %>/1.json
42
+ # PATCH/PUT <%= route_url %>/1 or <%= route_url %>/1.json
47
43
  def update
48
44
  respond_to do |format|
49
45
  if @<%= orm_instance.update("#{singular_table_name}_params") %>
50
- format.html { redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully updated.'" %> }
46
+ format.html { redirect_to <%= show_helper %>, notice: <%= %("#{human_name} was successfully updated.") %> }
51
47
  format.json { render :show, status: :ok, location: <%= "@#{singular_table_name}" %> }
52
48
  else
53
- format.html { render :edit }
49
+ format.html { render :edit, status: :unprocessable_entity }
54
50
  format.json { render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity }
55
51
  end
56
52
  end
57
53
  end
58
54
 
59
- # DELETE <%= route_url %>/1
60
- # DELETE <%= route_url %>/1.json
55
+ # DELETE <%= route_url %>/1 or <%= route_url %>/1.json
61
56
  def destroy
62
57
  @<%= orm_instance.destroy %>
58
+
63
59
  respond_to do |format|
64
- format.html { redirect_to <%= index_helper %>_url, notice: <%= "'#{human_name} was successfully destroyed.'" %> }
60
+ format.html { redirect_to <%= index_helper %>_url, notice: <%= %("#{human_name} was successfully destroyed.") %> }
65
61
  format.json { head :no_content }
66
62
  end
67
63
  end
@@ -72,12 +68,12 @@ class <%= controller_class_name %>Controller < ApplicationController
72
68
  @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
73
69
  end
74
70
 
75
- # Never trust parameters from the scary internet, only allow the white list through.
71
+ # Only allow a list of trusted parameters through.
76
72
  def <%= "#{singular_table_name}_params" %>
77
73
  <%- if attributes_names.empty? -%>
78
74
  params.fetch(<%= ":#{singular_table_name}" %>, {})
79
75
  <%- else -%>
80
- params.require(<%= ":#{singular_table_name}" %>).permit(<%= attributes_names.map { |name| ":#{name}" }.join(', ') %>)
76
+ params.require(<%= ":#{singular_table_name}" %>).permit(<%= permitted_params %>)
81
77
  <%- end -%>
82
78
  end
83
79
  end
@@ -1 +1 @@
1
- json.array! @<%= plural_table_name %>, partial: '<%= plural_table_name %>/<%= singular_table_name %>', as: :<%= singular_table_name %>
1
+ json.array! @<%= plural_table_name %>, partial: "<%= plural_table_name %>/<%= singular_table_name %>", as: :<%= singular_table_name %>
@@ -1,2 +1,16 @@
1
- json.extract! <%= singular_table_name %>, <%= attributes_list_with_timestamps %>
1
+ json.extract! <%= singular_table_name %>, <%= full_attributes_list %>
2
2
  json.url <%= singular_table_name %>_url(<%= singular_table_name %>, format: :json)
3
+ <%- virtual_attributes.each do |attribute| -%>
4
+ <%- if attribute.type == :rich_text -%>
5
+ json.<%= attribute.name %> <%= singular_table_name %>.<%= attribute.name %>.to_s
6
+ <%- elsif attribute.type == :attachment -%>
7
+ json.<%= attribute.name %> url_for(<%= singular_table_name %>.<%= attribute.name %>)
8
+ <%- elsif attribute.type == :attachments -%>
9
+ json.<%= attribute.name %> do
10
+ json.array!(<%= singular_table_name %>.<%= attribute.name %>) do |<%= attribute.singular_name %>|
11
+ json.id <%= attribute.singular_name %>.id
12
+ json.url url_for(<%= attribute.singular_name %>)
13
+ end
14
+ end
15
+ <%- end -%>
16
+ <%- end -%>
@@ -0,0 +1,109 @@
1
+ require 'delegate'
2
+ require 'active_support/concern'
3
+ require 'action_view'
4
+
5
+ begin
6
+ require 'action_view/renderer/collection_renderer'
7
+ rescue LoadError
8
+ require 'action_view/renderer/partial_renderer'
9
+ end
10
+
11
+ class Jbuilder
12
+ module CollectionRenderable # :nodoc:
13
+ extend ActiveSupport::Concern
14
+
15
+ class_methods do
16
+ def supported?
17
+ superclass.private_method_defined?(:build_rendered_template) && self.superclass.private_method_defined?(:build_rendered_collection)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def build_rendered_template(content, template, layout = nil)
24
+ super(content || json.attributes!, template)
25
+ end
26
+
27
+ def build_rendered_collection(templates, _spacer)
28
+ json.merge!(templates.map(&:body))
29
+ end
30
+
31
+ def json
32
+ @options[:locals].fetch(:json)
33
+ end
34
+
35
+ class ScopedIterator < ::SimpleDelegator # :nodoc:
36
+ include Enumerable
37
+
38
+ def initialize(obj, scope)
39
+ super(obj)
40
+ @scope = scope
41
+ end
42
+
43
+ # Rails 6.0 support:
44
+ def each
45
+ return enum_for(:each) unless block_given?
46
+
47
+ __getobj__.each do |object|
48
+ @scope.call { yield(object) }
49
+ end
50
+ end
51
+
52
+ # Rails 6.1 support:
53
+ def each_with_info
54
+ return enum_for(:each_with_info) unless block_given?
55
+
56
+ __getobj__.each_with_info do |object, info|
57
+ @scope.call { yield(object, info) }
58
+ end
59
+ end
60
+ end
61
+
62
+ private_constant :ScopedIterator
63
+ end
64
+
65
+ if defined?(::ActionView::CollectionRenderer)
66
+ # Rails 6.1 support:
67
+ class CollectionRenderer < ::ActionView::CollectionRenderer # :nodoc:
68
+ include CollectionRenderable
69
+
70
+ def initialize(lookup_context, options, &scope)
71
+ super(lookup_context, options)
72
+ @scope = scope
73
+ end
74
+
75
+ private
76
+ def collection_with_template(view, template, layout, collection)
77
+ super(view, template, layout, ScopedIterator.new(collection, @scope))
78
+ end
79
+ end
80
+ else
81
+ # Rails 6.0 support:
82
+ class CollectionRenderer < ::ActionView::PartialRenderer # :nodoc:
83
+ include CollectionRenderable
84
+
85
+ def initialize(lookup_context, options, &scope)
86
+ super(lookup_context)
87
+ @options = options
88
+ @scope = scope
89
+ end
90
+
91
+ def render_collection_with_partial(collection, partial, context, block)
92
+ render(context, @options.merge(collection: collection, partial: partial), block)
93
+ end
94
+
95
+ private
96
+ def collection_without_template(view)
97
+ @collection = ScopedIterator.new(@collection, @scope)
98
+
99
+ super(view)
100
+ end
101
+
102
+ def collection_with_template(view, template)
103
+ @collection = ScopedIterator.new(@collection, @scope)
104
+
105
+ super(view, template)
106
+ end
107
+ end
108
+ end
109
+ end
@@ -1,4 +1,5 @@
1
1
  require 'jbuilder/jbuilder'
2
+ require 'jbuilder/collection_renderer'
2
3
  require 'action_dispatch/http/mime_type'
3
4
  require 'active_support/cache'
4
5
 
@@ -15,6 +16,38 @@ class JbuilderTemplate < Jbuilder
15
16
  super(*args)
16
17
  end
17
18
 
19
+ # Generates JSON using the template specified with the `:partial` option. For example, the code below will render
20
+ # the file `views/comments/_comments.json.jbuilder`, and set a local variable comments with all this message's
21
+ # comments, which can be used inside the partial.
22
+ #
23
+ # Example:
24
+ #
25
+ # json.partial! 'comments/comments', comments: @message.comments
26
+ #
27
+ # There are multiple ways to generate a collection of elements as JSON, as ilustrated below:
28
+ #
29
+ # Example:
30
+ #
31
+ # json.array! @posts, partial: 'posts/post', as: :post
32
+ #
33
+ # # or:
34
+ # json.partial! 'posts/post', collection: @posts, as: :post
35
+ #
36
+ # # or:
37
+ # json.partial! partial: 'posts/post', collection: @posts, as: :post
38
+ #
39
+ # # or:
40
+ # json.comments @post.comments, partial: 'comments/comment', as: :comment
41
+ #
42
+ # Aside from that, the `:cached` options is available on Rails >= 6.0. This will cache the rendered results
43
+ # effectively using the multi fetch feature.
44
+ #
45
+ # Example:
46
+ #
47
+ # json.array! @posts, partial: "posts/post", as: :post, cached: true
48
+ #
49
+ # json.comments @post.comments, partial: "comments/comment", as: :comment, cached: true
50
+ #
18
51
  def partial!(*args)
19
52
  if args.one? && _is_active_model?(args.first)
20
53
  _render_active_model_partial args.first
@@ -73,8 +106,8 @@ class JbuilderTemplate < Jbuilder
73
106
  # json.cache_if! !admin?, @person, expires_in: 10.minutes do
74
107
  # json.extract! @person, :name, :age
75
108
  # end
76
- def cache_if!(condition, *args)
77
- condition ? cache!(*args, &::Proc.new) : yield
109
+ def cache_if!(condition, *args, &block)
110
+ condition ? cache!(*args, &block) : yield
78
111
  end
79
112
 
80
113
  def target!
@@ -104,11 +137,30 @@ class JbuilderTemplate < Jbuilder
104
137
  private
105
138
 
106
139
  def _render_partial_with_options(options)
107
- options.reverse_merge! locals: {}
140
+ options.reverse_merge! locals: options.except(:partial, :as, :collection, :cached)
108
141
  options.reverse_merge! ::JbuilderTemplate.template_lookup_options
109
142
  as = options[:as]
110
143
 
111
- if as && options.key?(:collection)
144
+ if as && options.key?(:collection) && CollectionRenderer.supported?
145
+ collection = options.delete(:collection) || []
146
+ partial = options.delete(:partial)
147
+ options[:locals].merge!(json: self)
148
+
149
+ if options.has_key?(:layout)
150
+ raise ::NotImplementedError, "The `:layout' option is not supported in collection rendering."
151
+ end
152
+
153
+ if options.has_key?(:spacer_template)
154
+ raise ::NotImplementedError, "The `:spacer_template' option is not supported in collection rendering."
155
+ end
156
+
157
+ results = CollectionRenderer
158
+ .new(@context.lookup_context, options) { |&block| _scope(&block) }
159
+ .render_collection_with_partial(collection, partial, @context, nil)
160
+
161
+ array! if results.respond_to?(:body) && results.body.nil?
162
+ elsif as && options.key?(:collection) && !CollectionRenderer.supported?
163
+ # For Rails <= 5.2:
112
164
  as = as.to_sym
113
165
  collection = options.delete(:collection)
114
166
  locals = options.delete(:locals)
@@ -151,8 +203,8 @@ class JbuilderTemplate < Jbuilder
151
203
  name_options = options.slice(:skip_digest, :virtual_path)
152
204
  key = _fragment_name_with_digest(key, name_options)
153
205
 
154
- if @context.respond_to?(:fragment_cache_key)
155
- key = @context.fragment_cache_key(key)
206
+ if @context.respond_to?(:combined_fragment_cache_key)
207
+ key = @context.combined_fragment_cache_key(key)
156
208
  else
157
209
  key = url_for(key).split('://', 2).last if ::Hash === key
158
210
  end
@@ -162,12 +214,7 @@ class JbuilderTemplate < Jbuilder
162
214
 
163
215
  def _fragment_name_with_digest(key, options)
164
216
  if @context.respond_to?(:cache_fragment_name)
165
- # Current compatibility, fragment_name_with_digest is private again and cache_fragment_name
166
- # should be used instead.
167
- @context.cache_fragment_name(key, options)
168
- elsif @context.respond_to?(:fragment_name_with_digest)
169
- # Backwards compatibility for period of time when fragment_name_with_digest was made public.
170
- @context.fragment_name_with_digest(key)
217
+ @context.cache_fragment_name(key, **options)
171
218
  else
172
219
  key
173
220
  end
@@ -222,11 +269,12 @@ end
222
269
 
223
270
  class JbuilderHandler
224
271
  cattr_accessor :default_format
225
- self.default_format = Mime[:json]
272
+ self.default_format = :json
226
273
 
227
- def self.call(template)
274
+ def self.call(template, source = nil)
275
+ source ||= template.source
228
276
  # this juggling is required to keep line numbers right in the error
229
- %{__already_defined = defined?(json); json||=JbuilderTemplate.new(self); #{template.source}
277
+ %{__already_defined = defined?(json); json||=JbuilderTemplate.new(self); #{source}
230
278
  json.target! unless (__already_defined && __already_defined != "method")}
231
279
  end
232
280
  end
@@ -1,4 +1,4 @@
1
- require 'rails/railtie'
1
+ require 'rails'
2
2
  require 'jbuilder/jbuilder_template'
3
3
 
4
4
  class Jbuilder
data/lib/jbuilder.rb CHANGED
@@ -1,19 +1,23 @@
1
+ require 'active_support'
1
2
  require 'jbuilder/jbuilder'
2
3
  require 'jbuilder/blank'
3
4
  require 'jbuilder/key_formatter'
4
5
  require 'jbuilder/errors'
5
- require 'multi_json'
6
+ require 'json'
6
7
  require 'ostruct'
8
+ require 'active_support/core_ext/hash/deep_merge'
7
9
 
8
10
  class Jbuilder
9
11
  @@key_formatter = nil
10
12
  @@ignore_nil = false
13
+ @@deep_format_keys = false
11
14
 
12
15
  def initialize(options = {})
13
16
  @attributes = {}
14
17
 
15
18
  @key_formatter = options.fetch(:key_formatter){ @@key_formatter ? @@key_formatter.clone : nil}
16
19
  @ignore_nil = options.fetch(:ignore_nil, @@ignore_nil)
20
+ @deep_format_keys = options.fetch(:deep_format_keys, @@deep_format_keys)
17
21
 
18
22
  yield self if ::Kernel.block_given?
19
23
  end
@@ -26,12 +30,12 @@ class Jbuilder
26
30
  BLANK = Blank.new
27
31
  NON_ENUMERABLES = [ ::Struct, ::OpenStruct ].to_set
28
32
 
29
- def set!(key, value = BLANK, *args)
33
+ def set!(key, value = BLANK, *args, &block)
30
34
  result = if ::Kernel.block_given?
31
35
  if !_blank?(value)
32
36
  # json.comments @post.comments { |comment| ... }
33
37
  # { "comments": [ { ... }, { ... } ] }
34
- _scope{ array! value, &::Proc.new }
38
+ _scope{ array! value, &block }
35
39
  else
36
40
  # json.comments { ... }
37
41
  # { "comments": ... }
@@ -42,11 +46,11 @@ class Jbuilder
42
46
  # json.age 32
43
47
  # json.person another_jbuilder
44
48
  # { "age": 32, "person": { ... }
45
- value.attributes!
49
+ _format_keys(value.attributes!)
46
50
  else
47
51
  # json.age 32
48
52
  # { "age": 32 }
49
- value
53
+ _format_keys(value)
50
54
  end
51
55
  elsif _is_collection?(value)
52
56
  # json.comments @post.comments, :content, :created_at
@@ -61,9 +65,9 @@ class Jbuilder
61
65
  _set_value key, result
62
66
  end
63
67
 
64
- def method_missing(*args)
68
+ def method_missing(*args, &block)
65
69
  if ::Kernel.block_given?
66
- set!(*args, &::Proc.new)
70
+ set!(*args, &block)
67
71
  else
68
72
  set!(*args)
69
73
  end
@@ -130,6 +134,31 @@ class Jbuilder
130
134
  @@ignore_nil = value
131
135
  end
132
136
 
137
+ # Deeply apply key format to nested hashes and arrays passed to
138
+ # methods like set!, merge! or array!.
139
+ #
140
+ # Example:
141
+ #
142
+ # json.key_format! camelize: :lower
143
+ # json.settings({some_value: "abc"})
144
+ #
145
+ # { "settings": { "some_value": "abc" }}
146
+ #
147
+ # json.key_format! camelize: :lower
148
+ # json.deep_format_keys!
149
+ # json.settings({some_value: "abc"})
150
+ #
151
+ # { "settings": { "someValue": "abc" }}
152
+ #
153
+ def deep_format_keys!(value = true)
154
+ @deep_format_keys = value
155
+ end
156
+
157
+ # Same as instance method deep_format_keys! except sets the default.
158
+ def self.deep_format_keys(value = true)
159
+ @@deep_format_keys = value
160
+ end
161
+
133
162
  # Turns the current element into an array and yields a builder to add a hash.
134
163
  #
135
164
  # Example:
@@ -163,7 +192,7 @@ class Jbuilder
163
192
  #
164
193
  # [ { "name": David", "age": 32 }, { "name": Jamie", "age": 31 } ]
165
194
  #
166
- # If you are using Ruby 1.9+, you can use the call syntax instead of an explicit extract! call:
195
+ # You can use the call syntax instead of an explicit extract! call:
167
196
  #
168
197
  # json.(@people) { |person| ... }
169
198
  #
@@ -181,18 +210,18 @@ class Jbuilder
181
210
  # json.array! [1, 2, 3]
182
211
  #
183
212
  # [1,2,3]
184
- def array!(collection = [], *attributes)
213
+ def array!(collection = [], *attributes, &block)
185
214
  array = if collection.nil?
186
215
  []
187
216
  elsif ::Kernel.block_given?
188
- _map_collection(collection, &::Proc.new)
217
+ _map_collection(collection, &block)
189
218
  elsif attributes.any?
190
219
  _map_collection(collection) { |element| extract! element, *attributes }
191
220
  else
192
- collection.to_a
221
+ _format_keys(collection.to_a)
193
222
  end
194
223
 
195
- merge! array
224
+ @attributes = _merge_values(@attributes, array)
196
225
  end
197
226
 
198
227
  # Extracts the mentioned attributes or hash elements from the passed object and turns them into attributes of the JSON.
@@ -220,9 +249,9 @@ class Jbuilder
220
249
  end
221
250
  end
222
251
 
223
- def call(object, *attributes)
252
+ def call(object, *attributes, &block)
224
253
  if ::Kernel.block_given?
225
- array! object, &::Proc.new
254
+ array! object, &block
226
255
  else
227
256
  extract! object, *attributes
228
257
  end
@@ -240,24 +269,25 @@ class Jbuilder
240
269
  @attributes
241
270
  end
242
271
 
243
- # Merges hash or array into current builder.
244
- def merge!(hash_or_array)
245
- @attributes = _merge_values(@attributes, hash_or_array)
272
+ # Merges hash, array, or Jbuilder instance into current builder.
273
+ def merge!(object)
274
+ hash_or_array = ::Jbuilder === object ? object.attributes! : object
275
+ @attributes = _merge_values(@attributes, _format_keys(hash_or_array))
246
276
  end
247
277
 
248
278
  # Encodes the current builder as JSON.
249
279
  def target!
250
- ::MultiJson.dump(@attributes)
280
+ @attributes.to_json
251
281
  end
252
282
 
253
283
  private
254
284
 
255
285
  def _extract_hash_values(object, attributes)
256
- attributes.each{ |key| _set_value key, object.fetch(key) }
286
+ attributes.each{ |key| _set_value key, _format_keys(object.fetch(key)) }
257
287
  end
258
288
 
259
289
  def _extract_method_values(object, attributes)
260
- attributes.each{ |key| _set_value key, object.public_send(key) }
290
+ attributes.each{ |key| _set_value key, _format_keys(object.public_send(key)) }
261
291
  end
262
292
 
263
293
  def _merge_block(key)
@@ -275,7 +305,7 @@ class Jbuilder
275
305
  elsif ::Array === current_value && ::Array === updates
276
306
  current_value + updates
277
307
  elsif ::Hash === current_value && ::Hash === updates
278
- current_value.merge(updates)
308
+ current_value.deep_merge(updates)
279
309
  else
280
310
  raise MergeError.build(current_value, updates)
281
311
  end
@@ -285,6 +315,18 @@ class Jbuilder
285
315
  @key_formatter ? @key_formatter.format(key) : key.to_s
286
316
  end
287
317
 
318
+ def _format_keys(hash_or_array)
319
+ return hash_or_array unless @deep_format_keys
320
+
321
+ if ::Array === hash_or_array
322
+ hash_or_array.map { |value| _format_keys(value) }
323
+ elsif ::Hash === hash_or_array
324
+ ::Hash[hash_or_array.collect { |k, v| [_key(k), _format_keys(v)] }]
325
+ else
326
+ hash_or_array
327
+ end
328
+ end
329
+
288
330
  def _set_value(key, value)
289
331
  raise NullError.build(key) if @attributes.nil?
290
332
  raise ArrayError.build(key) if ::Array === @attributes
@@ -300,12 +342,12 @@ class Jbuilder
300
342
  end
301
343
 
302
344
  def _scope
303
- parent_attributes, parent_formatter = @attributes, @key_formatter
345
+ parent_attributes, parent_formatter, parent_deep_format_keys = @attributes, @key_formatter, @deep_format_keys
304
346
  @attributes = BLANK
305
347
  yield
306
348
  @attributes
307
349
  ensure
308
- @attributes, @key_formatter = parent_attributes, parent_formatter
350
+ @attributes, @key_formatter, @deep_format_keys = parent_attributes, parent_formatter, parent_deep_format_keys
309
351
  end
310
352
 
311
353
  def _is_collection?(object)
@@ -53,7 +53,7 @@ class JbuilderDependencyTrackerTest < ActiveSupport::TestCase
53
53
  assert_equal %w[path/to/partial], dependencies
54
54
  end
55
55
 
56
- test 'detects partial in indirect collecton calls' do
56
+ test 'detects partial in indirect collection calls' do
57
57
  dependencies = track_dependencies <<-RUBY
58
58
  json.comments @post.comments, partial: 'comments/comment', as: :comment
59
59
  RUBY
@@ -61,7 +61,7 @@ class JbuilderDependencyTrackerTest < ActiveSupport::TestCase
61
61
  assert_equal %w[comments/comment], dependencies
62
62
  end
63
63
 
64
- test 'detects explicit depedency' do
64
+ test 'detects explicit dependency' do
65
65
  dependencies = track_dependencies <<-RUBY
66
66
  # Template Dependency: path/to/partial
67
67
  json.foo 'bar'
@@ -21,18 +21,38 @@ class JbuilderGeneratorTest < Rails::Generators::TestCase
21
21
  run_generator
22
22
 
23
23
  assert_file 'app/views/posts/index.json.jbuilder' do |content|
24
- assert_match %r{json.array! @posts, partial: 'posts/post', as: :post}, content
24
+ assert_match %r{json\.array! @posts, partial: "posts/post", as: :post}, content
25
25
  end
26
26
 
27
27
  assert_file 'app/views/posts/show.json.jbuilder' do |content|
28
- assert_match %r{json.partial! \"posts/post\", post: @post}, content
28
+ assert_match %r{json\.partial! "posts/post", post: @post}, content
29
29
  end
30
-
31
- assert_file 'app/views/posts/_post.json.jbuilder' do |content|
30
+
31
+ assert_file 'app/views/posts/_post.json.jbuilder' do |content|
32
32
  assert_match %r{json\.extract! post, :id, :title, :body}, content
33
+ assert_match %r{:created_at, :updated_at}, content
33
34
  assert_match %r{json\.url post_url\(post, format: :json\)}, content
34
35
  end
35
-
36
+ end
37
+
38
+ test 'timestamps are not generated in partial with --no-timestamps' do
39
+ run_generator %w(Post title body:text --no-timestamps)
40
+
41
+ assert_file 'app/views/posts/_post.json.jbuilder' do |content|
42
+ assert_match %r{json\.extract! post, :id, :title, :body$}, content
43
+ assert_no_match %r{:created_at, :updated_at}, content
44
+ end
45
+ end
36
46
 
47
+ if Rails::VERSION::MAJOR >= 6
48
+ test 'handles virtual attributes' do
49
+ run_generator %w(Message content:rich_text video:attachment photos:attachments)
50
+
51
+ assert_file 'app/views/messages/_message.json.jbuilder' do |content|
52
+ assert_match %r{json\.content message\.content\.to_s}, content
53
+ assert_match %r{json\.video url_for\(message\.video\)}, content
54
+ assert_match %r{json\.photos do\n json\.array!\(message\.photos\) do \|photo\|\n json\.id photo\.id\n json\.url url_for\(photo\)\n end\nend}, content
55
+ end
56
+ end
37
57
  end
38
58
  end