props_template 0.22.4 → 0.24.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: d1775b28ee0009735bc63ccb79aa1b22ad397c07682699ac384568ccec3704be
4
- data.tar.gz: 286f275535b658c80a2f5e8bb965a7cdeb21ef559ecfb7096447180422d4e1fd
3
+ metadata.gz: ed3fa0a5fda4993c730d2391de3e2a1d7f05e43580dc5e429734b5f8e7b07391
4
+ data.tar.gz: 005ed8fa15532e3e80530fae2a4c1bbd0002d9b9dcf5e2a41c64d34c62df2db0
5
5
  SHA512:
6
- metadata.gz: ac6ac10322e53936d8535f0a89c82d4dac4894932f4ac2739bc1f68f1faddea2a7ca81d7c65bbf04dccd49991ce9c26908676157b42e5ade62dee7faed89aa22
7
- data.tar.gz: 5227b5b577af671e582bfc3e27b64c9e45c90bec592238833fbb7312cb54b370db748c6ed502d4389179b7d617cf31789c00719330958a4c95435f9b3824233a
6
+ metadata.gz: 2a65e69050103126e02af67ebbf44af9817edd221467fef1ae6f9c715964b8c212ecc9d3a8796e9ec0868e395911668f11176dae3ce5c722620f9eccb326cede
7
+ data.tar.gz: b59b42c55eee9fb209093c38aeab2d369da005a6be41feecd73ecef62f6323b61ac41e879e005b40b584817d80e2b8873fb37c2c62dc22015177053371b5c08f
data/README.md CHANGED
@@ -29,7 +29,7 @@ conveniences and magic.
29
29
  json.flash flash.to_h
30
30
 
31
31
  json.menu do
32
- # all keys will be formatted as camelCase
32
+ # By default, all keys will be formatted as camelCase. See #change_key_format
33
33
 
34
34
  json.current_user do
35
35
  json.email current_user.email
@@ -94,7 +94,8 @@ You can also add a [layout](#layouts).
94
94
 
95
95
  ### json.set! or json.\<your key here\>
96
96
 
97
- Defines the attribute or structure. All keys are automatically camelized lower.
97
+ Defines the attribute or structure. All keys are automatically camelized lower
98
+ by default. See [Change Key Format](#change-key-format) to change this behavior.
98
99
 
99
100
  ```ruby
100
101
  json.set! :author_details, {...options} do
@@ -584,6 +585,23 @@ json.flash flash.to_h
584
585
  **NOTE** PropsTemplate inverts the usual Rails rendering flow. PropsTemplate
585
586
  will render Layout first, then the template when `yield json` is used.
586
587
 
588
+ ## Change key format
589
+ By default, all keys will be formatted with `camelized(:lower)`. If you want to
590
+ change this behavior, override it in an initializer:
591
+
592
+ ```ruby
593
+ Props::BaseWithExtensions.class_eval do
594
+ def key_format(key)
595
+ key.to_s
596
+ end
597
+ end
598
+ ```
599
+
600
+ ## Escape mode
601
+
602
+ PropsTemplate runs OJ with `mode: :rails`, which escapes HTML and XML characters
603
+ such as `&` and `<`.
604
+
587
605
  ## Contributing
588
606
 
589
607
  See the [CONTRIBUTING] document. Thank you, [contributors]!
@@ -593,7 +611,9 @@ See the [CONTRIBUTING] document. Thank you, [contributors]!
593
611
 
594
612
  ## Special Thanks
595
613
 
596
- Thanks to [turbostreamer](https://github.com/malomalo/turbostreamer) for the
597
- inspiration.
614
+ Thanks to [turbostreamer], [oj], and [jbuilder] for the inspiration.
598
615
 
599
616
  [1]: https://github.com/thoughtbot/superglue
617
+ [turbostreamer]: https://github.com/malomalo/turbostreamer
618
+ [jbuilder]: https://github.com/rails/jbuilder
619
+ [oj]: https://github.com/ohler55/oj/
@@ -1,9 +1,9 @@
1
- require 'oj'
2
- require 'active_support'
3
- #todo: active_support/core_ext/string/output_safety.rb
1
+ require "oj"
2
+ require "active_support"
4
3
 
5
4
  module Props
6
5
  class InvalidScopeForArrayError < StandardError; end
6
+
7
7
  class InvalidScopeForObjError < StandardError; end
8
8
 
9
9
  class Base
@@ -35,7 +35,7 @@ module Props
35
35
 
36
36
  def set!(key, value = nil)
37
37
  if @scope == :array
38
- raise InvalidScopeForObjError.new('Attempted to set! on an array! scope')
38
+ raise InvalidScopeForObjError.new("Attempted to set! on an array! scope")
39
39
  end
40
40
 
41
41
  if @scope.nil?
@@ -81,19 +81,19 @@ module Props
81
81
  collection.each_with_index do |item, index|
82
82
  pass_opts = all_opts[index]
83
83
  handle_collection_item(collection, item, index, pass_opts) do
84
- #todo: remove index?
84
+ # todo: remove index?
85
85
  yield item, index
86
86
  end
87
87
  end
88
88
  end
89
89
 
90
- #todo, add ability to define contents of array
90
+ # todo, add ability to define contents of array
91
91
  def array!(collection, options = {})
92
92
  if @scope.nil?
93
93
  @scope = :array
94
94
  @stream.push_array
95
95
  else
96
- raise InvalidScopeForArrayError.new('array! expects exclusive use of this block')
96
+ raise InvalidScopeForArrayError.new("array! expects exclusive use of this block")
97
97
  end
98
98
 
99
99
  handle_collection(collection, options) do |item, index|
@@ -108,10 +108,8 @@ module Props
108
108
  def result!
109
109
  if @scope.nil?
110
110
  @stream.push_object
111
- @stream.pop
112
- else
113
- @stream.pop
114
111
  end
112
+ @stream.pop
115
113
 
116
114
  json = @stream.raw_json
117
115
  @stream.reset
@@ -5,7 +5,7 @@ module Props
5
5
  def initialize(builder, context = nil, options = {})
6
6
  @context = context
7
7
  @builder = builder
8
- #todo: refactor so deferred can be its own class
8
+ # todo: refactor so deferred can be its own class
9
9
  @em = ExtensionManager.new(self)
10
10
  @traveled_path = []
11
11
  @key_cache = {}
@@ -50,7 +50,7 @@ module Props
50
50
  end
51
51
 
52
52
  def set!(key, options = {}, &block)
53
- if block_given?
53
+ if block
54
54
  options = @em.refine_options(options)
55
55
  end
56
56
 
@@ -60,7 +60,7 @@ module Props
60
60
  def handle_set_block(key, options)
61
61
  @traveled_path.push(key)
62
62
  n = 1
63
- if suffix = options[:path_suffix]
63
+ if (suffix = options[:path_suffix])
64
64
  n += suffix.length
65
65
  @traveled_path.push(suffix)
66
66
  end
@@ -68,7 +68,7 @@ module Props
68
68
  super
69
69
 
70
70
  @traveled_path.pop(n)
71
- return
71
+ nil
72
72
  end
73
73
 
74
74
  def handle_collection_item(collection, item, index, options)
@@ -80,14 +80,14 @@ module Props
80
80
  if id.nil?
81
81
  @traveled_path.push(index)
82
82
  else
83
- @traveled_path.push("#{id.to_s}=#{val}")
83
+ @traveled_path.push("#{id}=#{val}")
84
84
  end
85
85
  end
86
86
 
87
87
  super
88
88
 
89
89
  @traveled_path.pop
90
- return
90
+ nil
91
91
  end
92
92
 
93
93
  def refine_all_item_options(all_options)
@@ -97,7 +97,7 @@ module Props
97
97
  def refine_item_options(item, options)
98
98
  return options if options.empty?
99
99
 
100
- if key = options[:key]
100
+ if (key = options[:key])
101
101
  val = if item.respond_to? key
102
102
  item.send(key)
103
103
  elsif item.is_a? Hash
@@ -111,4 +111,3 @@ module Props
111
111
  end
112
112
  end
113
113
  end
114
-
@@ -7,7 +7,7 @@ module Props
7
7
  end
8
8
 
9
9
  def member_by(attribute, value)
10
- raise NotImplementedError, 'Implement member_by(attr, value) in your own delegate'
10
+ raise NotImplementedError, "Implement member_by(attr, value) in your own delegate"
11
11
  end
12
12
  end
13
13
  end
@@ -1,16 +1,15 @@
1
1
  # This was taken from jbuilder
2
- require 'props_template'
3
-
2
+ require "props_template"
4
3
 
5
4
  dependency_tracker = false
6
5
 
7
6
  begin
8
- require 'action_view'
9
- require 'action_view/dependency_tracker'
7
+ require "action_view"
8
+ require "action_view/dependency_tracker"
10
9
  dependency_tracker = ::ActionView::DependencyTracker
11
10
  rescue LoadError
12
11
  begin
13
- require 'cache_digests'
12
+ require "cache_digests"
14
13
  dependency_tracker = ::CacheDigests::DependencyTracker
15
14
  rescue LoadError
16
15
  end
@@ -2,7 +2,7 @@ module Props
2
2
  class ExtensionManager
3
3
  attr_reader :base, :builder, :context
4
4
 
5
- def initialize(base, defered=[], fragments=[])
5
+ def initialize(base, defered = [], fragments = [])
6
6
  @base = base
7
7
  @context = base.context
8
8
  @builder = base.builder
@@ -15,16 +15,14 @@ module Props
15
15
  def refine_options(options, item = nil)
16
16
  options = @partialer.refine_options(options, item)
17
17
  options = @deferment.refine_options(options, item)
18
- options = Cache.refine_options(options, item)
19
- options
18
+ Cache.refine_options(options, item)
20
19
  end
21
20
 
22
21
  def refine_all_item_options(all_options)
23
22
  return all_options if all_options.empty?
24
23
 
25
24
  all_options = @partialer.find_and_add_template(all_options)
26
- all_options = @cache.multi_fetch_and_add_results(all_options)
27
- all_options
25
+ @cache.multi_fetch_and_add_results(all_options)
28
26
  end
29
27
 
30
28
  def deferred
@@ -79,10 +77,10 @@ module Props
79
77
  yield
80
78
  meta = Oj.dump([deferred_paths, fragment_paths]).strip
81
79
  json_in_progress = base.stream.to_s
82
- if json_in_progress[start] == ','
80
+ if json_in_progress[start] == ","
83
81
  start += 1
84
82
  end
85
- raw = base.stream.to_s[start..-1].strip
83
+ raw = base.stream.to_s[start..].strip
86
84
  result = "#{meta}\n#{raw}"
87
85
  }
88
86
  result
@@ -21,9 +21,7 @@ module Props
21
21
  @context = context
22
22
  end
23
23
 
24
- def context
25
- @context
26
- end
24
+ attr_reader :context
27
25
 
28
26
  def multi_fetch(keys, options = {})
29
27
  result = {}
@@ -38,12 +36,12 @@ module Props
38
36
 
39
37
  payload = {
40
38
  controller_name: controller.controller_name,
41
- action_name: controller.action_name,
39
+ action_name: controller.action_name
42
40
  }
43
41
 
44
42
  read_caches = {}
45
43
 
46
- ActiveSupport::Notifications.instrument('read_multi_fragments.action_view', payload) do |payload|
44
+ ActiveSupport::Notifications.instrument("read_multi_fragments.action_view", payload) do |payload|
47
45
  read_caches = ::Rails.cache.read_multi(*ckeys, options)
48
46
  payload[:read_caches] = read_caches
49
47
  end
@@ -60,31 +58,30 @@ module Props
60
58
  first_opts = all_options[0]
61
59
 
62
60
  if first_opts[:cache] && controller.perform_caching
63
- keys = all_options.map{|i| i[:cache][0]}
61
+ keys = all_options.map { |i| i[:cache][0] }
64
62
  c_opts = first_opts[:cache][1]
65
63
  result = multi_fetch(keys, c_opts)
66
64
 
67
65
  all_options.map do |opts|
68
- key = opts[:cache][0]
66
+ key = opts[:cache][0]
69
67
 
70
68
  if result.key? key
71
69
  opts[:cache][1][:result] = result[key]
72
- opts
73
- else
74
- opts
75
70
  end
71
+
72
+ opts
76
73
  end
77
74
  else
78
75
  all_options
79
76
  end
80
77
  end
81
78
 
82
- #Copied from jbuilder
79
+ # Copied from jbuilder
83
80
  #
84
81
 
85
- def cache(key=nil, options={})
82
+ def cache(key = nil, options = {})
86
83
  if controller.perform_caching
87
- value = cache_fragment_for(key, options) do
84
+ cache_fragment_for(key, options) do
88
85
  yield
89
86
  end
90
87
  else
@@ -120,12 +117,11 @@ module Props
120
117
 
121
118
  if @context.respond_to?(:combined_fragment_cache_key)
122
119
  key = @context.combined_fragment_cache_key(key)
123
- else
124
- key = url_for(key).split('://', 2).last if ::Hash === key
120
+ elsif ::Hash === key
121
+ key = url_for(key).split("://", 2).last
125
122
  end
126
123
 
127
124
  ::ActiveSupport::Cache.expand_cache_key(key, :props)
128
125
  end
129
126
  end
130
127
  end
131
-
@@ -17,7 +17,7 @@ module Props
17
17
  }
18
18
 
19
19
  if item
20
- type = Proc === type ? type.call(item) : type
20
+ type = (Proc === type) ? type.call(item) : type
21
21
  end
22
22
 
23
23
  if type
@@ -44,10 +44,10 @@ module Props
44
44
  end
45
45
 
46
46
  request_path = @base.context.controller.request.fullpath
47
- path = @base.traveled_path.join('.')
47
+ path = @base.traveled_path.join(".")
48
48
  uri = ::URI.parse(request_path)
49
- qry = ::URI.decode_www_form(uri.query || '')
50
- .reject{|x| x[0] == 'props_at' }
49
+ qry = ::URI.decode_www_form(uri.query || "")
50
+ .reject { |x| x[0] == "props_at" }
51
51
  .push(["props_at", path])
52
52
 
53
53
  uri.query = ::URI.encode_www_form(qry)
@@ -55,7 +55,7 @@ module Props
55
55
  deferral = {
56
56
  url: uri.to_s,
57
57
  path: path,
58
- type: type.to_s,
58
+ type: type.to_s
59
59
  }
60
60
 
61
61
  # camelize for JS land
@@ -2,7 +2,7 @@ module Props
2
2
  class Fragment
3
3
  attr_reader :fragments
4
4
 
5
- def initialize(base, fragments=[])
5
+ def initialize(base, fragments = [])
6
6
  @base = base
7
7
  @fragments = fragments
8
8
  end
@@ -14,11 +14,11 @@ module Props
14
14
 
15
15
  if String === fragment || Symbol === fragment
16
16
  fragment_name = fragment.to_s
17
- path = @base.traveled_path.join('.')
17
+ path = @base.traveled_path.join(".")
18
18
  @name = fragment_name
19
19
 
20
20
  @fragments.push(
21
- { type: fragment_name, partial: partial_name, path: path }
21
+ {type: fragment_name, partial: partial_name, path: path}
22
22
  )
23
23
  end
24
24
  end
@@ -1,4 +1,4 @@
1
- require 'action_view'
1
+ require "action_view"
2
2
 
3
3
  module Props
4
4
  class RenderedTemplate
@@ -23,7 +23,6 @@ module Props
23
23
  IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " \
24
24
  "make sure your partial name starts with underscore."
25
25
 
26
-
27
26
  def initialize(base, context, builder)
28
27
  @context = context
29
28
  @builder = builder
@@ -67,7 +66,7 @@ module Props
67
66
  template_keys = retrieve_template_keys(partial_opts)
68
67
  details = extract_details(partial_opts)
69
68
 
70
- prefixes = partial.include?(?/) ? [] : @context.lookup_context.prefixes
69
+ prefixes = partial.include?("/") ? [] : @context.lookup_context.prefixes
71
70
  @context.lookup_context.find_template(partial, prefixes, true, template_keys, details)
72
71
  end
73
72
 
@@ -81,13 +80,12 @@ module Props
81
80
  partial, pass_opts = [*options[:partial]]
82
81
  pass_opts ||= {}
83
82
  pass_opts[:locals] ||= {}
84
- pass_opts[:locals][:json] = @builder
85
83
  pass_opts[:partial] = partial
86
84
  pass_opts[:formats] = [:json]
87
85
  pass_opts.delete(:handlers)
88
86
 
89
87
  if !(String === partial)
90
- raise ArgumentError.new(INVALID_PARTIAL_MESSAGE % (partial.inspect))
88
+ raise ArgumentError.new(INVALID_PARTIAL_MESSAGE % partial.inspect)
91
89
  end
92
90
 
93
91
  pass_opts
@@ -121,15 +119,15 @@ module Props
121
119
  end
122
120
 
123
121
  def raise_invalid_option_as(as)
124
- raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as))
122
+ raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % as)
125
123
  end
126
124
 
127
125
  def raise_invalid_identifier(path)
128
- raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
126
+ raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % path)
129
127
  end
130
128
 
131
129
  def retrieve_variable(path)
132
- base = path[-1] == "/" ? "" : File.basename(path)
130
+ base = (path[-1] == "/") ? "" : File.basename(path)
133
131
  raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/
134
132
  $1.to_sym
135
133
  end
@@ -153,7 +151,7 @@ module Props
153
151
 
154
152
  locals[as] = item
155
153
 
156
- if fragment_name = rest[:fragment]
154
+ if (fragment_name = rest[:fragment])
157
155
  rest[:fragment] = fragment_name.to_s
158
156
  end
159
157
  end
@@ -1,4 +1,4 @@
1
- require 'active_support'
1
+ require "active_support"
2
2
 
3
3
  module Props
4
4
  class Handler
@@ -8,8 +8,8 @@ module Props
8
8
  def self.call(template, source = nil)
9
9
  source ||= template.source
10
10
  # this juggling is required to keep line numbers right in the error
11
- %{__already_defined = defined?(json); json||=Props::Template.new(self); #{source};
12
- json.result! unless (__already_defined && __already_defined != "method")
11
+ %{ __finalize = !defined?(@__json); @__json ||= Props::Template.new(self); json = @__json; #{source};
12
+ json.result! if __finalize
13
13
  }
14
14
  end
15
15
  end
@@ -1,54 +1,35 @@
1
1
  module Props
2
2
  module LayoutPatch
3
- def render(context, options)
4
- options[:locals] ||= {}
5
- options[:locals][:json] = nil
6
-
7
- @details = extract_details(options)
8
- template = determine_template(options)
9
-
10
- if template.respond_to?(:handler) && template.handler == Props::Handler && options[:layout]
3
+ def render_template(view, template, layout_name, locals)
4
+ if template.respond_to?(:handler) && template.handler == Props::Handler && layout_name
11
5
  prepend_formats(template.format)
12
- render_props_template(context, template, options[:layout], options[:locals])
6
+ render_props_template(view, template, layout_name, locals)
13
7
  else
14
- super(context, options)
8
+ super
15
9
  end
16
10
  end
17
11
 
18
12
  def render_props_template(view, template, path, locals)
13
+ view.instance_eval <<~RUBY, __FILE__, __LINE__ + 1
14
+ def virtual_path_of_template;"#{template.virtual_path}";end
15
+ RUBY
16
+
17
+ # Deprecated: Usage of virtual_path_of_template in local_assigns will
18
+ # be removed for a method definition above
19
19
  layout_locals = locals.dup
20
- layout_locals.delete(:json)
21
20
  layout_locals[:virtual_path_of_template] = template.virtual_path
22
21
 
23
- layout = resolve_props_layout(path, layout_locals.keys, [formats.first])
24
- body = layout.render(view, layout_locals) do |json|
25
- locals[:json] = json
22
+ layout = resolve_layout(path, layout_locals.keys, [formats.first])
23
+ body = if layout
24
+ layout.render(view, layout_locals) do |json|
25
+ template.render(view, locals)
26
+ end
27
+ else
26
28
  template.render(view, locals)
27
29
  end
28
30
 
29
31
  build_rendered_template(body, template)
30
32
  end
31
-
32
- def resolve_props_layout(layout, keys, formats)
33
- details = @details.dup
34
- details[:formats] = formats
35
-
36
- case layout
37
- when String
38
- begin
39
- if layout.start_with?("/")
40
- ActiveSupport::Deprecation.warn "Rendering layouts from an absolute path is deprecated."
41
- @lookup_context.with_fallbacks.find_template(layout, nil, false, [], details)
42
- else
43
- @lookup_context.find_template(layout, nil, false, [], details)
44
- end
45
- end
46
- when Proc
47
- resolve_layout(layout.call(@lookup_context, formats), keys, formats)
48
- else
49
- layout
50
- end
51
- end
52
33
  end
53
34
  end
54
35
 
@@ -1,13 +1,13 @@
1
- require 'rails/railtie'
2
- require 'props_template'
1
+ require "rails/railtie"
2
+ require "props_template"
3
3
 
4
4
  module Props
5
5
  class Railtie < ::Rails::Railtie
6
6
  initializer :props_template do
7
7
  ActiveSupport.on_load :action_view do
8
8
  ActionView::Template.register_template_handler :props, Props::Handler
9
- require 'props_template/dependency_tracker'
10
- require 'props_template/layout_patch'
9
+ require "props_template/dependency_tracker"
10
+ require "props_template/layout_patch"
11
11
  end
12
12
  end
13
13
  end
@@ -2,7 +2,7 @@ module Props
2
2
  class Searcher
3
3
  attr_reader :builder, :context, :fragments, :traveled_path
4
4
 
5
- def initialize(builder, path=[], context = nil)
5
+ def initialize(builder, path = [], context = nil)
6
6
  @search_path = path
7
7
  @depth = 0
8
8
  @context = context
@@ -24,7 +24,7 @@ module Props
24
24
  def found!
25
25
  pass_opts = @found_options.clone || {}
26
26
  pass_opts.delete(:defer)
27
- traveled_path = @traveled_path[1..-1] || []
27
+ traveled_path = @traveled_path[1..] || []
28
28
  if !traveled_path.empty?
29
29
  pass_opts[:path_suffix] = traveled_path
30
30
  end
@@ -36,8 +36,8 @@ module Props
36
36
  yield
37
37
  end
38
38
 
39
- def set!(key, options={}, &block)
40
- return if @found_block || !block_given?
39
+ def set!(key, options = {}, &block)
40
+ return if @found_block || !block
41
41
 
42
42
  if @search_path[@depth] == key.to_s
43
43
  @traveled_path.push(key)
@@ -64,7 +64,7 @@ module Props
64
64
  return if @found_block
65
65
 
66
66
  key_index = @search_path[@depth]
67
- id_name, id_val = key_index.to_s.split('=')
67
+ id_name, id_val = key_index.to_s.split("=")
68
68
 
69
69
  if id_val
70
70
  id_val = id_val.to_i
@@ -80,7 +80,7 @@ module Props
80
80
 
81
81
  if @depth == @search_path.size - 1
82
82
  @found_options = pass_opts
83
- @found_block = Proc.new {
83
+ @found_block = proc {
84
84
  yield item, 0
85
85
  }
86
86
  return
@@ -1,3 +1,3 @@
1
1
  module Props
2
- VERSION = "0.22.4".freeze
2
+ VERSION = "0.24.0".freeze
3
3
  end
@@ -1,17 +1,17 @@
1
- require 'props_template/base'
2
- require 'props_template/extensions/partial_renderer'
3
- require 'props_template/extensions/cache'
4
- require 'props_template/extensions/deferment'
5
- require 'props_template/extensions/fragment'
6
- require 'props_template/base_with_extensions'
7
- require 'props_template/extension_manager'
8
- require 'active_support/core_ext/string/output_safety'
9
- require 'active_support/core_ext/array'
10
- require 'props_template/searcher'
11
- require 'props_template/handler'
12
- require 'props_template/version'
13
-
14
- require 'active_support'
1
+ require "props_template/base"
2
+ require "props_template/extensions/partial_renderer"
3
+ require "props_template/extensions/cache"
4
+ require "props_template/extensions/deferment"
5
+ require "props_template/extensions/fragment"
6
+ require "props_template/base_with_extensions"
7
+ require "props_template/extension_manager"
8
+ require "active_support/core_ext/string/output_safety"
9
+ require "active_support/core_ext/array"
10
+ require "props_template/searcher"
11
+ require "props_template/handler"
12
+ require "props_template/version"
13
+
14
+ require "active_support"
15
15
 
16
16
  module Props
17
17
  class Template
@@ -19,7 +19,7 @@ module Props
19
19
  attr_accessor :template_lookup_options
20
20
  end
21
21
 
22
- self.template_lookup_options = { handlers: [:props] }
22
+ self.template_lookup_options = {handlers: [:props]}
23
23
 
24
24
  delegate :result!, :array!,
25
25
  :deferred!,
@@ -34,7 +34,7 @@ module Props
34
34
  end
35
35
 
36
36
  def set!(key, options = {}, &block)
37
- if block_given? && options[:search] && !@builder.is_a?(Searcher)
37
+ if block && options[:search] && !@builder.is_a?(Searcher)
38
38
 
39
39
  prev_builder = @builder
40
40
  @builder = Searcher.new(self, options[:search], @context)
@@ -60,4 +60,4 @@ module Props
60
60
  end
61
61
  end
62
62
 
63
- require 'props_template/railtie' if defined?(Rails)
63
+ require "props_template/railtie" if defined?(Rails)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: props_template
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.22.4
4
+ version: 0.24.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Johny Ho
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-14 00:00:00.000000000 Z
11
+ date: 2023-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -65,7 +65,6 @@ files:
65
65
  - lib/props_template/base.rb
66
66
  - lib/props_template/base_with_extensions.rb
67
67
  - lib/props_template/core_ext.rb
68
- - lib/props_template/debug_writer.rb
69
68
  - lib/props_template/dependency_tracker.rb
70
69
  - lib/props_template/extension_manager.rb
71
70
  - lib/props_template/extensions/cache.rb
@@ -77,9 +76,6 @@ files:
77
76
  - lib/props_template/railtie.rb
78
77
  - lib/props_template/searcher.rb
79
78
  - lib/props_template/version.rb
80
- - spec/layout_spec.rb
81
- - spec/props_template_spec.rb
82
- - spec/searcher_spec.rb
83
79
  homepage: https://github.com/thoughtbot/props_template/
84
80
  licenses:
85
81
  - MIT
@@ -103,7 +99,4 @@ rubygems_version: 3.1.6
103
99
  signing_key:
104
100
  specification_version: 4
105
101
  summary: A fast JSON builder
106
- test_files:
107
- - spec/searcher_spec.rb
108
- - spec/layout_spec.rb
109
- - spec/props_template_spec.rb
102
+ test_files: []
@@ -1,55 +0,0 @@
1
- require 'oj'
2
- require 'active_support'
3
- require 'byebug'
4
-
5
- module Props
6
- class DebugWriter
7
- attr_accessor :commands
8
-
9
- def initialize(opts)
10
- @stream = Oj::StringWriter.new(opts)
11
- @commands = []
12
- end
13
-
14
- def push_object
15
- @commands.push([:push_object])
16
- end
17
-
18
- def push_key(key)
19
- @commands.push([:push_key, key])
20
- end
21
-
22
- def push_value(value, key = nil)
23
- if key
24
- @commands.push([:push_value, value, key])
25
- else
26
- @commands.push([:push_value, value])
27
- end
28
- end
29
-
30
- def push_array
31
- @commands.push([:push_array])
32
- end
33
-
34
- def pop
35
- @commands.push([:pop])
36
- end
37
-
38
- def reset
39
- @commands = []
40
- @stream.reset
41
- end
42
-
43
- def to_s
44
- @commands.each do |command|
45
- begin
46
- @stream.send(*command)
47
- rescue => e
48
- byebug
49
- end
50
- end
51
-
52
- @stream.to_s
53
- end
54
- end
55
- end
data/spec/layout_spec.rb DELETED
@@ -1,24 +0,0 @@
1
- require_relative "./support/helper"
2
- require_relative "./support/rails_helper"
3
- require "props_template/layout_patch"
4
- require "action_controller"
5
-
6
- RSpec.describe "Props::Template" do
7
- class TestController < ActionController::Base
8
- protect_from_forgery
9
-
10
- def self.controller_path
11
- ""
12
- end
13
- end
14
-
15
- it "uses a layout to render" do
16
- view_path = File.join(File.dirname(__FILE__), "./fixtures")
17
- controller = TestController.new
18
- controller.prepend_view_path(view_path)
19
-
20
- json = controller.render_to_string("200", layout: "application")
21
-
22
- expect(json.strip).to eql('{"data":{"success":"ok"}}')
23
- end
24
- end
@@ -1,300 +0,0 @@
1
- require_relative './support/helper'
2
-
3
- RSpec.describe 'Props::Base' do
4
- it 'initializes' do
5
- expect {
6
- Props::Base.new
7
- }.to_not raise_error
8
- end
9
-
10
- context 'result!' do
11
- it 'returns {} when empty' do
12
- json = Props::Base.new
13
- expect(json.result!.strip).to eql('{}')
14
- end
15
-
16
- it 'resets OJ' do
17
- json = Props::Base.new
18
- json.set! :foo, 'bar'
19
- attrs = json.result!.strip
20
-
21
- expect(attrs).to eql_json({
22
- foo: 'bar'
23
- })
24
-
25
- expect(json.result!.strip).to eql_json({})
26
-
27
- json.set! :foo, 'bar'
28
- attrs = json.result!.strip
29
- expect(attrs).to eql_json({
30
- foo: 'bar'
31
- })
32
- end
33
- end
34
-
35
- context 'set!' do
36
- it 'sets a value' do
37
- json = Props::Base.new
38
- json.set! :foo, 'bar'
39
- attrs = json.result!.strip
40
-
41
- expect(attrs).to eql_json({
42
- foo: 'bar'
43
- })
44
- end
45
-
46
- it 'sets a empty obj when block is empty' do
47
- json = Props::Base.new
48
- json.set! :foo do
49
- end
50
- attrs = json.result!.strip
51
-
52
- expect(attrs).to eql_json({
53
- foo: {}
54
- })
55
- end
56
-
57
- it 'sets a empty obj when nested block is empty' do
58
- json = Props::Base.new
59
- json.set! :foo do
60
- json.set! :bar do
61
- end
62
- end
63
- attrs = json.result!.strip
64
-
65
- expect(attrs).to eql_json({
66
- foo: {
67
- bar: {}
68
- }
69
- })
70
- end
71
-
72
- it 'sets a null value' do
73
- json = Props::Base.new
74
- json.set! :foo, nil
75
- attrs = json.result!.strip
76
-
77
- expect(attrs).to eql_json({
78
- foo: nil
79
- })
80
- end
81
-
82
- it 'sets multiple values' do
83
- json = Props::Base.new
84
- json.set! :foo, 'bar'
85
- json.set! :steve, 'cool'
86
-
87
- attrs = json.result!.strip
88
-
89
- expect(attrs).to eql_json({
90
- foo: 'bar',
91
- steve: 'cool'
92
- })
93
- end
94
-
95
- it 'sets multiple values with the same key, the last one wins' do
96
- json = Props::Base.new
97
- json.set! :foo, 'bar'
98
- json.set! :foo, 'cool'
99
-
100
- attrs = JSON.parse(json.result!)
101
-
102
- expect(attrs).to eql({
103
- 'foo' => 'cool'
104
- })
105
- end
106
-
107
- it 'throws InvalidScopeForObjError when the current scope is an array' do
108
- json = Props::Base.new
109
- json.array! [1, 2] do |item|
110
- json.set! :foo, item
111
- end
112
- expect {
113
- json.set! :bar, 'world'
114
- }.to raise_error(Props::InvalidScopeForObjError)
115
- end
116
- end
117
-
118
- context 'set! with a block' do
119
- it 'creates a new nested object' do
120
- json = Props::Base.new
121
- json.set! :outer do
122
- json.set! :inner, 'baz'
123
- end
124
-
125
- attrs = json.result!.strip
126
-
127
- expect(attrs).to eql_json({
128
- outer: {
129
- inner: 'baz'
130
- }
131
- })
132
- end
133
-
134
- it 'creates a nested object' do
135
- json = Props::Base.new
136
- json.set! :outer do
137
- json.set! :inner, 'baz'
138
- end
139
-
140
- attrs = json.result!.strip
141
-
142
- expect(attrs).to eql_json({
143
- outer: {
144
- inner: 'baz'
145
- }
146
- })
147
- end
148
-
149
- it 'creates a nested array of objects' do
150
- json = Props::Base.new
151
- json.set! :outer do
152
- json.array! [1, 2] do |item|
153
- json.set! :foo, item
154
- end
155
- end
156
-
157
- attrs = json.result!.strip
158
-
159
- expect(attrs).to eql_json({
160
- outer: [
161
- {foo: 1},
162
- {foo: 2}
163
- ]
164
- })
165
- end
166
-
167
- it 'treats the second argument as an options arg' do
168
- json = Props::Base.new
169
- json.set! :outer, {some: 'setting'} do
170
- json.set! :inner, 'baz'
171
- end
172
-
173
- attrs = json.result!.strip
174
-
175
- expect(attrs).to eql_json({
176
- outer: {
177
- inner: 'baz'
178
- }
179
- })
180
- end
181
- end
182
-
183
- context 'array!' do
184
- it 'creates an array of 1 object' do
185
- json = Props::Base.new
186
- json.array! [1] do |num|
187
- json.set! :foo, num
188
- end
189
-
190
- attrs = json.result!.strip
191
-
192
- expect(attrs).to eql_json([
193
- {foo: 1}
194
- ])
195
- end
196
-
197
- it 'passes the index as the second argument of yield' do
198
- json = Props::Base.new
199
- json.array! ['a', 'b'] do |item, index|
200
- json.set! :foo, [item, index]
201
- end
202
-
203
- attrs = json.result!.strip
204
-
205
- expect(attrs).to eql_json([
206
- {foo: ['a', 0]},
207
- {foo: ['b', 1]}
208
- ])
209
- end
210
-
211
- it 'creates an empty array when passed an empty collection' do
212
- json = Props::Base.new
213
- json.array! [] do |num|
214
- end
215
-
216
- attrs = json.result!.strip
217
-
218
- expect(attrs).to eql_json([
219
- ])
220
- end
221
-
222
- it 'creates an array of empty arrays when passed an empty collection' do
223
- json = Props::Base.new
224
- json.array! [1] do |num|
225
- json.array! [] do
226
- end
227
- end
228
-
229
- attrs = json.result!.strip
230
- expect(attrs).to eql_json([[]])
231
- end
232
-
233
- it 'creates an array of empties if set did not get called in array' do
234
- json = Props::Base.new
235
- json.array! [1, 2, 3] do |num|
236
- end
237
-
238
- attrs = json.result!.strip
239
-
240
- expect(attrs).to eql_json([
241
- {},
242
- {},
243
- {}
244
- ])
245
- end
246
-
247
- it 'throws InvalidScopeForArray error when array is used twice' do
248
- json = Props::Base.new
249
- json.array! [1] do
250
- json.set! :foo, 'first'
251
- end
252
-
253
- expect {
254
- json.array! [2] do |num|
255
- json.set! :foo, 'second'
256
- end
257
- }.to raise_error(Props::InvalidScopeForArrayError)
258
- end
259
-
260
- it 'creates an array of multiple objects' do
261
- json = Props::Base.new
262
- json.array! [1, 2] do |num|
263
- json.set! :foo, num
264
- end
265
-
266
- attrs = json.result!.strip
267
-
268
- expect(attrs).to eql_json([
269
- {foo: 1},
270
- {foo: 2}
271
- ])
272
- end
273
-
274
- it 'creates an array of arrays' do
275
- json = Props::Base.new
276
- json.array! [[1, 2], [3, 4]] do |part|
277
- json.array! part do |num|
278
- json.set! :foo, num
279
- end
280
- end
281
-
282
- attrs = json.result!.strip
283
-
284
- expect(attrs).to eql_json([
285
- [{foo: 1}, {foo: 2}],
286
- [{foo: 3}, {foo: 4}]
287
- ])
288
- end
289
-
290
- it 'throws InvalidScopeForArrayError when the current scope is already an object' do
291
- json = Props::Base.new
292
- json.set! :foo, 'bar'
293
- expect {
294
- json.array! [1, 2] do |num|
295
- json.set! :foo, 'noop'
296
- end
297
- }.to raise_error(Props::InvalidScopeForArrayError)
298
- end
299
- end
300
- end
@@ -1,209 +0,0 @@
1
- require_relative './support/helper'
2
-
3
- RSpec.describe 'Searcher' do
4
- it 'searching for a child node returns the proc, and refined found options' do
5
- json = Props::Searcher.new(nil, ['outer', 'inner', 'hit'])
6
- target_proc = Proc.new {}
7
- target_opts = {some_options: 1}
8
- json.set!('outer') do
9
- json.set!('inner') do
10
- json.set!('hit', target_opts, &target_proc)
11
- end
12
- end
13
-
14
- found_block, found_options = json.found!
15
- expect(found_block).to eql(target_proc)
16
- expect(found_options).to eql({
17
- some_options: 1,
18
- path_suffix: ['inner', 'hit']
19
- })
20
- end
21
-
22
- it 'searching with an empty path means you found nothing' do
23
- json = Props::Searcher.new(nil, [])
24
-
25
- target_proc = Proc.new do
26
- json.set!('inner') do
27
- json.set!('hit', {}, &target_proc)
28
- end
29
- end
30
- json.set!('outer', {}, &target_proc)
31
-
32
- found_block, found_options = json.found!
33
- expect(found_block).to be_nil
34
- end
35
-
36
- it 'searching for a child node with siblings in back' do
37
- json = Props::Searcher.new(nil, ['outer', 'inner'])
38
- target_proc = Proc.new do
39
- json.set!('foo', 32)
40
- end
41
-
42
- json.set!('outer') do
43
- json.set!('inner', {}, &target_proc)
44
-
45
- json.set!('bad') do
46
- json.set!('foo', 'should not touch')
47
- end
48
- end
49
-
50
- found_block, found_options = json.found!
51
- expect(found_block).to eql(target_proc)
52
- end
53
-
54
- it 'searching for a child node with siblings in front' do
55
- json = Props::Searcher.new(nil, ['outer', 'inner'])
56
- target_proc = Proc.new do
57
- json.foo 32
58
- end
59
-
60
- json.set!('outer') do
61
- json.set!('bad') do
62
- json.set!('foo', 'should not touch')
63
- end
64
-
65
- json.set!('inner', {}, &target_proc)
66
- end
67
-
68
- found_block, found_options = json.found!
69
- expect(found_block).to eql(target_proc)
70
- end
71
-
72
- it 'searching for a subtree' do
73
- json = Props::Searcher.new(nil, ['outer', 'inner', 'deep'])
74
-
75
- target_proc = Proc.new do
76
- json.set!('deeper') do
77
- json.set!('foo', 32)
78
- end
79
- end
80
-
81
- json.set!('outer') do
82
- json.set!('inner') do
83
- json.set!('deep', {}, &target_proc)
84
- end
85
- end
86
-
87
- found_block, found_options = json.found!
88
- expect(found_block).to eql(target_proc)
89
- end
90
-
91
- it 'searching for a leaf node is unsupported' do
92
- json = Props::Searcher.new(nil, ['outer', 'inner', 'foo'])
93
- json.set!('outer') do
94
- json.set!('inner') do
95
- json.set!('foo', 32)
96
- end
97
- end
98
-
99
- found_block, found_options = json.found!
100
- expect(found_block).to be_nil
101
- end
102
-
103
- it 'searching for a node beyond whats available is equivalent to not finding anything' do
104
- json = Props::Searcher.new(nil, ['outer', 'inner', 'a', 'b'])
105
- json.set!('outer') do
106
- json.set!('inner') do
107
- json.set!('foo', 32)
108
- end
109
- end
110
-
111
- found_block, found_options = json.found!
112
- expect(found_block).to be_nil
113
- end
114
-
115
- it 'searching for an item inside an array, includes refined found options' do
116
- json = Props::Searcher.new(nil, ['outer', 1])
117
- target_opts = {some_options: 1}
118
- json.set!('outer') do
119
- json.array!(['hello', 'world'], target_opts) do |item|
120
- item
121
- end
122
- end
123
-
124
- found_block, found_options = json.found!
125
- expect(found_options).to eql({some_options: 1, path_suffix: [1]})
126
- expect(found_block.call).to eql('world')
127
- end
128
-
129
- it 'searching for an item inside an array using an id=val keypath' do
130
- json = Props::Searcher.new(nil, ['outer', 'id=1'])
131
-
132
- class Collection
133
- def initialize(ary, rspec)
134
- @ary = ary
135
- @rspec = rspec
136
- end
137
-
138
- def member_by(key, value)
139
- @rspec.expect(key).to @rspec.eql('id')
140
- @rspec.expect(value).to @rspec.eql(1)
141
- @ary.last
142
- end
143
- end
144
-
145
- collection = Collection.new(
146
- [{id: 0}, {id: 1}],
147
- self
148
- )
149
-
150
- json.set!('outer') do
151
- json.array!(collection) do |item|
152
- item
153
- end
154
- end
155
-
156
- found_block, found_options = json.found!
157
- expect(found_block.call).to eql({id: 1})
158
- end
159
-
160
- it 'searching for an node belonging an array' do
161
- json = Props::Searcher.new(nil, ['outer', 'inner', 1, 'foo'])
162
- json.set!('outer') do
163
- json.set!('inner') do
164
- json.array! ['hello', 'world'] do |item|
165
- json.set!('foo') do
166
- item
167
- end
168
- end
169
- end
170
- end
171
- found_block, found_options = json.found!
172
- expect(found_block.call).to eql('world')
173
- end
174
-
175
- it 'searching for an node outside the length of the array, is equivalent to not finding anything' do
176
- json = Props::Searcher.new(nil, ['outer','inner', 10, 'foo'])
177
- json.set!('outer') do
178
- json.set!('inner') do
179
- json.array! [1, 2] do |item|
180
- json.set!('foo') do
181
- json.set!('bar', item)
182
- end
183
- end
184
- end
185
- end
186
-
187
- found_block, found_options = json.found!
188
- expect(found_block).to be_nil
189
- end
190
-
191
- it 'searching for object inside a nested array' do
192
- json = Props::Searcher.new(nil, ['outer', 'inner', 1, 'foo', 0])
193
- json.set!('outer') do
194
- json.set!('inner') do
195
- json.array! [0, 1] do |item|
196
- json.set!('foo') do
197
- json.array! ['hello', 'world'] do |inner_item|
198
- inner_item
199
- end
200
- end
201
- end
202
- end
203
- end
204
-
205
- found_block, found_options = json.found!
206
- expect(found_block.call).to eql('hello')
207
- end
208
- end
209
-