props_template 0.13.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.
@@ -0,0 +1,150 @@
1
+ module Props
2
+ class Cache
3
+ delegate :controller, :safe_concat, to: :@context
4
+
5
+ def self.refine_options(options, item = nil)
6
+ return options if !options[:cache]
7
+
8
+ pass_opts = options.clone
9
+ key, rest = [*options[:cache]]
10
+ rest ||= {}
11
+
12
+ if item && ::Proc === key
13
+ key = key.call(item)
14
+ end
15
+
16
+ pass_opts[:cache] = [key, rest]
17
+ pass_opts
18
+ end
19
+
20
+ def initialize(context)
21
+ @context = context
22
+ end
23
+
24
+ def context
25
+ @context
26
+ end
27
+
28
+ def instrument(name, **options)
29
+ ActiveSupport::Notifications.instrument(name, options) do |payload|
30
+ yield payload
31
+ end
32
+ end
33
+
34
+ def multi_fetch(keys, options = {})
35
+ result = {}
36
+ key_to_ckey = {}
37
+ ckeys = []
38
+
39
+ keys.each do |k|
40
+ ckey = cache_key(k, options)
41
+ ckeys.push(ckey)
42
+ key_to_ckey[k] = ckey
43
+ end
44
+
45
+ payload = {
46
+ controller_name: controller.controller_name,
47
+ action_name: controller.action_name,
48
+ }
49
+
50
+ read_caches = {}
51
+
52
+ instrument('read_multi_fragments.action_view', payload) do |payload|
53
+ read_caches = ::Rails.cache.read_multi(*ckeys, options)
54
+ payload[:read_caches] = read_caches
55
+ end
56
+
57
+ keys.each do |k|
58
+ ckey = key_to_ckey[k]
59
+ result[k] = read_caches[ckey]
60
+ end
61
+
62
+ result
63
+ end
64
+
65
+ def multi_fetch_and_add_results(all_options)
66
+ first_opts = all_options[0]
67
+
68
+ if first_opts[:cache] && controller.perform_caching
69
+ keys = all_options.map{|i| i[:cache][0]}
70
+ c_opts = first_opts[:cache][1]
71
+ result = multi_fetch(keys, c_opts)
72
+
73
+ all_options.map do |opts|
74
+ key = opts[:cache][0]
75
+
76
+ if result.key? key
77
+ opts[:cache][1][:result] = result[key]
78
+ opts
79
+ else
80
+ opts
81
+ end
82
+ end
83
+ else
84
+ all_options
85
+ end
86
+ end
87
+
88
+ #Copied from jbuilder
89
+ #
90
+
91
+ def cache(key=nil, options={})
92
+ if controller.perform_caching
93
+ value = cache_fragment_for(key, options) do
94
+ yield
95
+ end
96
+ else
97
+ yield
98
+ end
99
+ end
100
+
101
+ def cache_fragment_for(key, options, &block)
102
+ key = cache_key(key, options)
103
+
104
+ return options[:result] if options[:result]
105
+
106
+ read_fragment_cache(key, options) || write_fragment_cache(key, options, &block)
107
+ end
108
+
109
+ def read_fragment_cache(key, options = nil)
110
+ controller.instrument_fragment_cache :read_fragment, key do
111
+ ::Rails.cache.read(key, options)
112
+ end
113
+ end
114
+
115
+ def write_fragment_cache(key, options = nil)
116
+ controller.instrument_fragment_cache :write_fragment, key do
117
+ yield.tap do |value|
118
+ ::Rails.cache.write(key, value, options)
119
+ end
120
+ end
121
+ end
122
+
123
+ def cache_key(key, options)
124
+ name_options = options.slice(:skip_digest, :virtual_path)
125
+ key = fragment_name_with_digest(key, name_options)
126
+
127
+ if @context.respond_to?(:combined_fragment_cache_key)
128
+ key = @context.combined_fragment_cache_key(key)
129
+ else
130
+ key = url_for(key).split('://', 2).last if ::Hash === key
131
+ end
132
+
133
+ ::ActiveSupport::Cache.expand_cache_key(key, :props)
134
+ end
135
+
136
+ def fragment_name_with_digest(key, options)
137
+ if @context.respond_to?(:cache_fragment_name)
138
+ # Current compatibility, fragment_name_with_digest is private again and cache_fragment_name
139
+ # should be used instead.
140
+ @context.cache_fragment_name(key, options)
141
+ elsif @context.respond_to?(:fragment_name_with_digest)
142
+ # Backwards compatibility for period of time when fragment_name_with_digest was made public.
143
+ @context.fragment_name_with_digest(key)
144
+ else
145
+ key
146
+ end
147
+ end
148
+ end
149
+ end
150
+
@@ -0,0 +1,56 @@
1
+ module Props
2
+ class Deferment
3
+ attr_reader :deferred
4
+
5
+ def initialize(base, deferred = [])
6
+ @deferred = deferred
7
+ @base = base
8
+ end
9
+
10
+ def refine_options(options, item = nil)
11
+ return options if !options[:defer]
12
+ pass_opts = options.clone
13
+
14
+ type, rest = [*options[:defer]]
15
+ rest ||= {
16
+ placeholder: {}
17
+ }
18
+
19
+ if item
20
+ type = Proc === type ? type.call(item) : type
21
+ end
22
+
23
+ if type
24
+ pass_opts[:defer] = [type, rest]
25
+ else
26
+ pass_opts.delete(:defer)
27
+ end
28
+
29
+ pass_opts
30
+ end
31
+
32
+ def handle(options)
33
+ return if !options[:defer]
34
+
35
+ type, rest = options[:defer]
36
+ placeholder = rest[:placeholder]
37
+
38
+ request_path = @base.context.controller.request.fullpath
39
+ path = @base.traveled_path.join('.')
40
+ uri = ::URI.parse(request_path)
41
+ qry = ::URI.decode_www_form(uri.query || '')
42
+ .reject{|x| x[0] == 'bzq' }
43
+ .push(["bzq", path])
44
+
45
+ uri.query = ::URI.encode_www_form(qry)
46
+
47
+ @deferred.push(
48
+ url: uri.to_s,
49
+ path: path,
50
+ type: type.to_s
51
+ )
52
+
53
+ placeholder
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,50 @@
1
+ require 'digest'
2
+
3
+ module Props
4
+ class Fragment
5
+ attr_reader :fragments
6
+ attr_accessor :name
7
+
8
+ def initialize(base, fragments={})
9
+ @base = base
10
+ @fragments = fragments
11
+ @digest = Digest::SHA2.new(256)
12
+ end
13
+
14
+ def handle(options)
15
+ return if !options[:partial]
16
+ partial_name, partial_opts = options[:partial]
17
+ fragment = partial_opts[:fragment]
18
+
19
+ if String === fragment || Symbol === fragment
20
+ fragment_name = fragment.to_s
21
+ path = @base.traveled_path.join('.')
22
+ @name = fragment_name
23
+ @fragments[fragment_name] ||= []
24
+ @fragments[fragment_name].push(path)
25
+ end
26
+
27
+ if fragment == true
28
+ locals = partial_opts[:locals]
29
+
30
+ identity = {}
31
+ locals
32
+ .clone
33
+ .tap{|h| h.delete(:json)}
34
+ .each do |key, value|
35
+ if value.respond_to?(:to_global_id)
36
+ identity[key] = value.to_global_id.to_s
37
+ else
38
+ identity[key] = value
39
+ end
40
+ end
41
+
42
+ path = @base.traveled_path.join('.')
43
+ fragment_name = @digest.hexdigest("#{partial_name}#{identity.to_json}")
44
+ @name = fragment_name
45
+ @fragments[fragment_name] ||= []
46
+ @fragments[fragment_name].push(path)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,187 @@
1
+ require "concurrent/map"
2
+ require 'action_view'
3
+
4
+ module Props
5
+ class Partialer
6
+ def initialize(base, context, builder)
7
+ @context = context
8
+ @builder = builder
9
+ @base = base
10
+ end
11
+
12
+ def find_and_add_template(all_options)
13
+ first_opts = all_options[0]
14
+
15
+ if first_opts[:partial]
16
+ partial_opts = block_opts_to_render_opts(@builder, first_opts)
17
+ renderer = PartialRenderer.new(@context, partial_opts)
18
+
19
+ all_options.map do |opts|
20
+ opts[:_template] = renderer.template
21
+ opts
22
+ end
23
+ else
24
+ all_options
25
+ end
26
+ end
27
+
28
+ def block_opts_to_render_opts(builder, options)
29
+ partial, pass_opts = [*options[:partial]]
30
+ pass_opts ||= {}
31
+ pass_opts[:locals] ||= {}
32
+ pass_opts[:locals][:json] = @builder
33
+ pass_opts[:partial] = partial
34
+
35
+ pass_opts
36
+ end
37
+
38
+ def refine_options(options, item = nil)
39
+ PartialRenderer.refine_options(options, item)
40
+ end
41
+
42
+ def handle(options)
43
+ pass_opts = block_opts_to_render_opts(@builder, options)
44
+ renderer = PartialRenderer.new(@context, pass_opts)
45
+ template = options[:_template] || renderer.template
46
+
47
+ renderer.render(template, pass_opts)
48
+ end
49
+ end
50
+
51
+ class PartialRenderer < ActionView::AbstractRenderer
52
+ OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " \
53
+ "make sure it starts with lowercase letter, " \
54
+ "and is followed by any combination of letters, numbers and underscores."
55
+ IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " \
56
+ "make sure your partial name starts with underscore."
57
+
58
+ INVALID_PARTIAL_MESSAGE = "The partial name must be a string, but received (%s)."
59
+
60
+ def self.find_and_add_template(builder, context, all_options)
61
+ first_opts = all_options[0]
62
+
63
+ if first_opts[:partial]
64
+ partial_opts = block_opts_to_render_opts(builder, first_opts)
65
+ renderer = new(context, partial_opts)
66
+
67
+ all_options.map do |opts|
68
+ opts[:_template] = renderer.template
69
+ opts
70
+ end
71
+ else
72
+ all_options
73
+ end
74
+ end
75
+
76
+ def self.raise_invalid_option_as(as)
77
+ raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as))
78
+ end
79
+
80
+ def self.raise_invalid_identifier(path)
81
+ raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
82
+ end
83
+
84
+
85
+ def self.retrieve_variable(path)
86
+ base = path[-1] == "/" ? "" : File.basename(path)
87
+ raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/
88
+ $1.to_sym
89
+ end
90
+
91
+ def self.refine_options(options, item = nil)
92
+ return options if !options[:partial]
93
+
94
+ partial, rest = [*options[:partial]]
95
+ rest = (rest || {}).clone
96
+ locals = rest[:locals] || {}
97
+ rest[:locals] = locals
98
+
99
+ if item
100
+ as = if !rest[:as]
101
+ retrieve_variable(partial)
102
+ else
103
+ rest[:as].to_sym
104
+ end
105
+
106
+ raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s)
107
+
108
+ locals[as] = item
109
+
110
+ if fragment_name = rest[:fragment]
111
+ fragment_name = Proc === fragment_name ? fragment_name.call(item) : fragment_name.to_s
112
+ rest[:fragment] = fragment_name
113
+ end
114
+ end
115
+
116
+ pass_opts = options.clone
117
+ pass_opts[:partial] = [partial, rest]
118
+
119
+ pass_opts
120
+ end
121
+
122
+ attr_reader :template
123
+
124
+ def initialize(context, options)
125
+ @context = context
126
+ super(@context.lookup_context)
127
+ @options = options.merge(formats: [:json])
128
+ @options.delete(:handlers)
129
+ @details = extract_details(@options)
130
+
131
+ partial = @options[:partial]
132
+
133
+ if !(String === partial)
134
+ raise_invalid_partial(partial.inspect)
135
+ end
136
+
137
+ @path = partial
138
+ @context_prefix = @lookup_context.prefixes.first
139
+ template_keys = retrieve_template_keys(@options)
140
+ @template = find_template(@path, template_keys)
141
+ end
142
+
143
+ def render(template, options)
144
+ #remove this later
145
+
146
+ render_partial(template, @context, @options)
147
+ end
148
+
149
+ private
150
+
151
+ def render_partial(template, view, options)
152
+ template ||= @template
153
+ # @variable ||= template.variable
154
+
155
+ instrument(:partial, identifier: @template.identifier) do |payload|
156
+ locals = options[:locals]
157
+ content = template.render(view, locals)
158
+
159
+ payload[:cache_hit] = view.view_renderer.cache_hits[template.virtual_path]
160
+ build_rendered_template(content, template)
161
+ end
162
+ end
163
+
164
+ # Sets up instance variables needed for rendering a partial. This method
165
+ # finds the options and details and extracts them. The method also contains
166
+ # logic that handles the type of object passed in as the partial.
167
+ #
168
+ # If +options[:partial]+ is a string, then the <tt>@path</tt> instance variable is
169
+ # set to that string. Otherwise, the +options[:partial]+ object must
170
+ # respond to +to_partial_path+ in order to setup the path.
171
+
172
+ def find_template(path, locals)
173
+ prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
174
+ @lookup_context.find_template(path, prefixes, true, locals, @details)
175
+ end
176
+
177
+ def retrieve_template_keys(options)
178
+ template_keys = options[:locals].keys
179
+ template_keys << options[:as] if options[:as]
180
+ template_keys
181
+ end
182
+
183
+ def raise_invalid_partial(path)
184
+ raise ArgumentError.new(INVALID_PARTIAL_MESSAGE % (path))
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,16 @@
1
+ require 'active_support'
2
+
3
+ module Props
4
+ class Handler
5
+ cattr_accessor :default_format
6
+ self.default_format = :json
7
+
8
+ def self.call(template, source = nil)
9
+ source ||= template.source
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")
13
+ }
14
+ end
15
+ end
16
+ end