props_template 0.13.0

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