props_template 1.0.1 → 1.0.3

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: 7b3bde6ce57e684648932f2fc307d1ad885679ee21428b0c7ee66cc31c13ee67
4
- data.tar.gz: aa90aea1e4c0950702619b26183647b74f18f5bfd0fb18808ae8c8d347803ed4
3
+ metadata.gz: f31ca5a3810b9348f75dae05f2b5cb8927faaedf321a4a565914b5760968090c
4
+ data.tar.gz: 6709d2fc54d81aaf2977158bffc0754089343e7aa8d32d50cbfb01a54a5b289b
5
5
  SHA512:
6
- metadata.gz: aab15761d3b7edb82d1f2d637a8ca0dcd3ab2241caa74ab75d4e6226c82a5b513308194c670f7ce1d3ab3c6b80d8fd8111332f9bb4705acd3b70ac05564132bc
7
- data.tar.gz: d66cc1d55181b2fb948d393fcc4bac96fb91546ca999d1e3c6634b58309f47cf4b036ed1f7ad9671eb9e63e104e48e5c850907a1b6710d8790481f45b4f470d5
6
+ metadata.gz: 3d3c08391669747b2b3cecfae77e4c47893a1dd887e21ab999a56e7c5e58a3afed3ccdc8147f4bed75fe1c33a3b903975357e00b515e8f445c5f1d1fef9049bc
7
+ data.tar.gz: da253f3b0d7f903ef0683990e270a1e5f4140c57416d023cce6645ac0156580a66145cc1061cf74d0f2b300d3e8e570d6bc3683d2aa2f88f5427496ddf43fbb3
data/README.md CHANGED
@@ -5,7 +5,7 @@ support for Russian-Doll caching, layouts, and can be queried by giving the
5
5
  root a key path.
6
6
 
7
7
  [![Build
8
- Status](https://circleci.com/gh/thoughtbot/props_template.svg?style=shield)](https://circleci.com/gh/thoughtbot/props_template)
8
+ Status](https://github.com/thoughtbot/props_template/actions/workflows/build.yml/badge.svg)](https://github.com/thoughtbot/props_template/actions/workflows/build.yml)
9
9
 
10
10
  It's fast.
11
11
 
@@ -98,7 +98,7 @@ json.posts do
98
98
  end
99
99
 
100
100
  json.paginationPath posts_path
101
- json.current pagedPosts.current_page
101
+ json.current paged_posts.current_page
102
102
  json.total @posts.count
103
103
  end
104
104
 
@@ -131,6 +131,12 @@ json.greetings "hello world"
131
131
 
132
132
  You can also add a [layout](#layouts).
133
133
 
134
+ To generate `index` and `show` templates for a resource, use the generator:
135
+
136
+ ```
137
+ rails g props:views Post title:string body:text
138
+ ```
139
+
134
140
  ## API
135
141
 
136
142
  ### json.set! or json.\<your key here\>
@@ -144,13 +150,13 @@ end
144
150
 
145
151
  # or
146
152
 
147
- json.authorDetails, with.some_option do
153
+ json.authorDetails(with.some_option) do
148
154
  json.firstName 'David'
149
155
  end
150
156
 
151
157
  # or with hash options
152
158
 
153
- json.authorDetails, {...options} do
159
+ json.authorDetails({...options}) do
154
160
  json.firstName 'David'
155
161
  end
156
162
 
@@ -163,7 +169,7 @@ The inline form defines key and value
163
169
  | Parameter | Notes |
164
170
  | :--- | :--- |
165
171
  | key | A json object key|
166
- | value | A value or an [options][#options] object enabled with [partial](#partials)|
172
+ | value | A value or an [options](#options) object enabled with [partial](#partials)|
167
173
 
168
174
  ```ruby
169
175
 
@@ -268,7 +274,7 @@ class ObjectCollection < SimpleDelegator
268
274
 
269
275
  def member_by(attr, val)
270
276
  find do |ele|
271
- ele[attr] == val
277
+ ele[attr.to_sym] == val
272
278
  end
273
279
  end
274
280
  end
@@ -296,7 +302,7 @@ class ApplicationRecord < ActiveRecord::Base
296
302
  end
297
303
 
298
304
  def self.member_by(attr, value)
299
- find_by(Hash[attr, val])
305
+ find_by(Hash[attr, value])
300
306
  end
301
307
  end
302
308
  ```
@@ -309,6 +315,30 @@ json.array! Post.all do
309
315
  end
310
316
  ```
311
317
 
318
+ #### json.child!
319
+
320
+ To build an array of heterogeneous elements, call `json.array!` without a
321
+ collection and add each element with `json.child!`:
322
+
323
+ ```ruby
324
+ json.items do
325
+ json.array! do
326
+ json.child! do
327
+ json.title "first"
328
+ end
329
+
330
+ json.child! do
331
+ json.body "second"
332
+ end
333
+ end
334
+ end
335
+
336
+ # => {"items": [{"title": "first"}, {"body": "second"}]}
337
+ ```
338
+
339
+ `json.child!` can be [dug](#digging) into by index, e.g. `items.1`, but not by
340
+ attribute.
341
+
312
342
  #### **Array core extension**
313
343
 
314
344
  For convenience, PropsTemplate includes a core\_ext that adds these methods to
@@ -321,7 +351,7 @@ data = [
321
351
  {id: 2, name: 'bar'}
322
352
  ]
323
353
 
324
- json.posts
354
+ json.posts do
325
355
  json.array! data do
326
356
  # ...
327
357
  end
@@ -337,11 +367,11 @@ may still need to implement `member_by`.
337
367
  Returns all deferred nodes used by the [deferment](#deferment) option.
338
368
 
339
369
  **Note** This is a [SuperglueJS][1] specific functionality and is used in
340
- `application.json.props` when first running `rails superglue:install:web`
370
+ `application.json.props` when first running `rails g superglue:install`
341
371
 
342
372
 
343
373
  ```ruby
344
- json.deferred json.deferred!
374
+ json.defers json.deferred!
345
375
 
346
376
  # => [{url: '/some_url?props_at=outer.inner', path: 'outer.inner', type: 'auto'}]
347
377
  ```
@@ -353,10 +383,12 @@ to fetch missing data in a second round trip.
353
383
  Returns all fragment nodes used by the [partial fragments](#partial-fragments)
354
384
  option.
355
385
 
356
- ```ruby json.fragments json.fragments! ```
386
+ ```ruby
387
+ json.fragments json.fragments!
388
+ ```
357
389
 
358
390
  **Note** This is a [SuperglueJS][1] specific functionality and is used in
359
- `application.json.props` when first running `rails superglue:install:web`
391
+ `application.json.props` when first running `rails g superglue:install`
360
392
 
361
393
  ## Options
362
394
 
@@ -366,13 +398,13 @@ functionality such as Partials, Deferments, and Caching
366
398
  The following are equivalent:
367
399
 
368
400
  ```ruby
369
- json.post(with.partial('blog_post')
401
+ json.post(with.partial('blog_post'))
370
402
  ```
371
403
 
372
404
  or
373
405
 
374
406
  ```ruby
375
- json.post(Props::Options.new.partial('blog_post')
407
+ json.post(Props::Options.new.partial('blog_post'))
376
408
  ```
377
409
  ### Hash options
378
410
 
@@ -388,7 +420,7 @@ end
388
420
  ### Partials
389
421
 
390
422
  Partials are supported. The following will render the file
391
- `views/posts/_blog_posts.json.props`, and set a local variable `post` assigned
423
+ `views/posts/_blog_post.json.props`, and set a local variable `post` assigned
392
424
  with @post, which you can use inside the partial.
393
425
 
394
426
  ```ruby
@@ -456,12 +488,14 @@ json.profile do
456
488
  end
457
489
  ```
458
490
 
459
- When using fragments with Arrays, the argument **MUST** be a lamda:
491
+ When using fragments with Arrays, the argument can be a String, a Symbol, or a
492
+ lambda that receives each item and returns the fragment name. Return `nil` to
493
+ skip the fragment for that item:
460
494
 
461
495
  ```ruby
462
496
  require 'props_template/core_ext'
463
497
 
464
- json.array! ['foo', 'bar'], with.partial("footer").fragment(->(x){ x == 'foo'})
498
+ json.array! ['foo', 'bar'], with.partial("footer").fragment(->(x){ "footer_#{x}" })
465
499
  ```
466
500
 
467
501
  ### Caching
@@ -557,6 +591,19 @@ Finally in your `application.json.props`:
557
591
  json.defers json.deferred!
558
592
  ```
559
593
 
594
+ To render every deferred node in place (e.g. for a request that should get
595
+ the full page), call `json.disable_deferments!` before the deferred nodes:
596
+
597
+ ```ruby
598
+ json.disable_deferments!
599
+
600
+ json.dashboard(with.defer(:auto)) do
601
+ json.someFancyMetric 42
602
+ end
603
+
604
+ # => {"dashboard": {"someFancyMetric": 42}}
605
+ ```
606
+
560
607
  #### Working with arrays
561
608
  The default behavior for deferments is to use the index of the collection to
562
609
  identify an element.
@@ -570,8 +617,10 @@ If you wish to use an attribute to identify the element. You must:
570
617
  your collection item, and is used for `defer: :auto` to generate a keypath for
571
618
  [SuperglueJS][1]. If you are NOT using SuperglueJS, you do not need to do this.
572
619
 
573
- 2. Implement `member_at`, on the [collection](#jsonarray). This will be called
574
- by PropsTemplate to when [digging](#digging)
620
+ 2. Implement `member_by(attr, value)` on the [collection](#jsonarray). This will
621
+ be called by PropsTemplate when [digging](#digging) with a path like
622
+ `posts.id=1`. Note that `attr` is passed as a String and `value` is coerced
623
+ with `to_i`, so only integer attribute values are supported.
575
624
 
576
625
  For example:
577
626
 
@@ -582,7 +631,7 @@ data = [
582
631
  {id: 2, name: 'bar'}
583
632
  ]
584
633
 
585
- json.posts
634
+ json.posts do
586
635
  json.array! data, with.id_key(:some_id) do |item|
587
636
  # By using :key, props_template will append `json.some_id item.some_id`
588
637
  # automatically
@@ -686,7 +735,7 @@ The above will render:
686
735
  ```
687
736
 
688
737
  ## Layouts
689
- A single layout is supported. To use, create an `application.json.props` in
738
+ To use a layout, create an `application.json.props` in
690
739
  `app/views/layouts`. Here's an example:
691
740
 
692
741
  ```ruby
@@ -712,6 +761,23 @@ json.flash flash.to_h
712
761
  **NOTE** PropsTemplate inverts the usual Rails rendering flow. PropsTemplate
713
762
  will render Layout first, then the template when `yield json` is used.
714
763
 
764
+ ### Partials with layouts
765
+
766
+ Partials can also be rendered with a layout. The layout is itself a partial:
767
+
768
+ ```ruby
769
+ # app/views/posts/_stream_message.json.props
770
+ json.greeting "Hello world"
771
+
772
+ json.data do
773
+ yield
774
+ end
775
+ ```
776
+
777
+ ```ruby
778
+ render partial: "comment", layout: "stream_message"
779
+ ```
780
+
715
781
 
716
782
  ### Layouts in API-only Rails apps
717
783
 
@@ -747,7 +813,7 @@ Props::BaseWithExtensions.class_eval do
747
813
  # json.second_value "second"
748
814
  #
749
815
  # -> { "firstValue" => "first", "second_value" => "second" }
750
- def key_format(key)
816
+ def format_key(key)
751
817
  key.to_s
752
818
  end
753
819
  end
@@ -758,7 +824,7 @@ Props::BaseWithExtensions.class_eval do
758
824
  # json.second_value "second"
759
825
  #
760
826
  # -> { "firstValue" => "first", "secondValue" => "second" }
761
- def key_format(key)
827
+ def format_key(key)
762
828
  @key_cache ||= {}
763
829
  @key_cache[key] ||= key.to_s.camelize(:lower)
764
830
  @key_cache[key]
@@ -777,7 +843,7 @@ Props::BaseWithExtensions.class_eval do
777
843
  # json.second_value "second"
778
844
  #
779
845
  # -> { "first_value" => "first", "second_value" => "second" }
780
- def key_format(key)
846
+ def format_key(key)
781
847
  @key_cache ||= {}
782
848
  @key_cache[key] ||= key.to_s.underscore
783
849
  @key_cache[key]
@@ -0,0 +1,11 @@
1
+ json.<%= js_plural_table_name %> do
2
+ json.array! @<%= plural_table_name %> do |<%= singular_table_name %>|
3
+ <%- attributes_list_with_timestamps.each do |attr| -%>
4
+ json.<%=attr.to_s.camelize(:lower)%> <%= singular_table_name %>.<%=attr%>
5
+ <%- end -%>
6
+ json.edit<%=js_singular_table_name(:upper)%>Path edit_<%=singular_table_name%>_path(<%=singular_table_name%>)
7
+ json.<%=js_singular_table_name%>Path <%=singular_table_name%>_path(<%=singular_table_name%>)
8
+ end
9
+ end
10
+
11
+ json.new<%= js_singular_table_name(:upper) %>Path new_<%= singular_table_name %>_path
@@ -0,0 +1,6 @@
1
+ <%- attributes_list_with_timestamps.each do |attr|-%>
2
+ json.<%=attr.to_s.camelize(:lower)%> @<%= singular_table_name %>.<%=attr%>
3
+ <%- end -%>
4
+
5
+ json.<%= js_plural_table_name %>Path <%= plural_table_name %>_path
6
+ json.edit<%= js_singular_table_name(:upper) %>Path edit_<%= singular_table_name %>_path(@<%= singular_table_name %>)
@@ -0,0 +1,9 @@
1
+ Description:
2
+ Generates PropsTemplate view files (.json.props) for a given resource.
3
+
4
+ Example:
5
+ rails generate props:views Post title:string body:text
6
+
7
+ This will create:
8
+ app/views/posts/index.json.props
9
+ app/views/posts/show.json.props
@@ -0,0 +1,64 @@
1
+ require "rails/generators/named_base"
2
+ require "rails/generators/resource_helpers"
3
+
4
+ module Props
5
+ module Generators
6
+ class ViewsGenerator < Rails::Generators::NamedBase
7
+ include Rails::Generators::ResourceHelpers
8
+
9
+ source_root File.expand_path("../templates", __dir__)
10
+
11
+ argument :attributes, type: :array, default: [], banner: "field:type field:type"
12
+
13
+ def create_root_folder
14
+ path = File.join("app/views", controller_file_path)
15
+ empty_directory path unless File.directory?(path)
16
+ end
17
+
18
+ def copy_prop_files
19
+ available_views.each do |view|
20
+ @action_name = view
21
+ filename = filename_with_extensions(view)
22
+ template filename, File.join("app/views", controller_file_path, filename)
23
+ end
24
+ end
25
+
26
+ protected
27
+
28
+ def js_singular_table_name(casing = :lower)
29
+ singular_table_name.camelize(casing)
30
+ end
31
+
32
+ def js_plural_table_name(casing = :lower)
33
+ plural_table_name.camelize(casing)
34
+ end
35
+
36
+ def available_views
37
+ %w[index show]
38
+ end
39
+
40
+ attr_reader :action_name
41
+
42
+ def attributes_names
43
+ [:id] + super
44
+ end
45
+
46
+ def filename_with_extensions(name)
47
+ [name, :json, :props].join(".")
48
+ end
49
+
50
+
51
+ def attributes_list_with_timestamps
52
+ attributes_list(attributes_names + %w[created_at updated_at])
53
+ end
54
+
55
+ def attributes_list(attributes = attributes_names)
56
+ if self.attributes.any? { |attr| attr.name == "password" && attr.type == :digest }
57
+ attributes = attributes.reject { |name| %w[password password_confirmation].include? name }
58
+ end
59
+
60
+ attributes
61
+ end
62
+ end
63
+ end
64
+ end
@@ -36,7 +36,7 @@ module Props
36
36
  end
37
37
 
38
38
  def format_key(key)
39
- key.to_s
39
+ (::Symbol === key) ? key.name : key.to_s
40
40
  end
41
41
 
42
42
  def set!(key, value = nil)
@@ -46,10 +46,6 @@ module Props
46
46
  @em = ExtensionManager.new(self, prev_state[1], prev_state[2])
47
47
  end
48
48
 
49
- def format_key(key)
50
- key.to_s
51
- end
52
-
53
49
  def set!(key, options = {}, &block)
54
50
  if !block && options.is_a?(Props::Options)
55
51
  options.valid_for_set!
@@ -49,9 +49,15 @@ module Props
49
49
  partial = partial_opts[:partial]
50
50
  template_keys = retrieve_template_keys(partial_opts)
51
51
  details = extract_details(partial_opts)
52
+ lookup_context = @context.lookup_context
52
53
 
53
- prefixes = partial.include?("/") ? [] : @context.lookup_context.prefixes
54
- @context.lookup_context.find_template(partial, prefixes, true, template_keys, details)
54
+ prefixes = partial.include?("/") ? [] : lookup_context.prefixes
55
+
56
+ if lookup_context.respond_to?(:find!)
57
+ lookup_context.find!(partial, prefixes, true, template_keys, details)
58
+ else
59
+ lookup_context.find_template(partial, prefixes, true, template_keys, details)
60
+ end
55
61
  end
56
62
 
57
63
  def retrieve_template_keys(options)
@@ -1,3 +1,3 @@
1
1
  module Props
2
- VERSION = "1.0.1".freeze
2
+ VERSION = "1.0.3".freeze
3
3
  end
@@ -59,7 +59,11 @@ module Props
59
59
  @builder = prev_builder
60
60
  @fragment_path = fragment_path
61
61
 
62
- if found_block
62
+ # An inline options object, e.g. `json.foo(with.partial("foo"))`, has
63
+ # no block; its content comes from the options instead.
64
+ found_inline_options = found_options.is_a?(Props::Options)
65
+
66
+ if found_block || found_inline_options
63
67
  @builder.item_context = found_item
64
68
  set!(key, found_options, &found_block)
65
69
  end
@@ -72,8 +76,11 @@ module Props
72
76
  @context.render options
73
77
  end
74
78
 
79
+ # Relative to the fragment the dig ended in, so the client can graft into
80
+ # it; the full path when no fragment was crossed.
75
81
  def found_path!
76
- @found_path[@fragment_path.size..].join(".")
82
+ fragment_path = @fragment_path || []
83
+ @found_path[fragment_path.size..].join(".")
77
84
  end
78
85
 
79
86
  def fragment_context!
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: props_template
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Johny Ho
@@ -72,6 +72,10 @@ extensions: []
72
72
  extra_rdoc_files: []
73
73
  files:
74
74
  - README.md
75
+ - lib/generators/props/templates/index.json.props
76
+ - lib/generators/props/templates/show.json.props
77
+ - lib/generators/props/views/USAGE
78
+ - lib/generators/props/views/views_generator.rb
75
79
  - lib/props_template.rb
76
80
  - lib/props_template/base.rb
77
81
  - lib/props_template/base_with_extensions.rb
@@ -108,7 +112,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
112
  - !ruby/object:Gem::Version
109
113
  version: '0'
110
114
  requirements: []
111
- rubygems_version: 3.6.9
115
+ rubygems_version: 4.0.16
112
116
  specification_version: 4
113
117
  summary: A fast JSON builder
114
118
  test_files: []