Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
strategy:
fail-fast: false
matrix:
ruby: ["3.4", "3.3", "3.2"]
ruby: ["4.0", "3.4", "3.3"]
version: ["72", "80", "81", "main"]

runs-on: "ubuntu-latest"
Expand Down
3 changes: 2 additions & 1 deletion lib/props_template.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,14 @@ def set!(key, options = {}, &block)
options.delete(:dig)

@builder.set!(key, options, &block)
found_block, found_path, found_options, fragment_path, fragment_context = @builder.found!
found_block, found_path, found_options, fragment_path, fragment_context, found_item = @builder.found!
@found_path = found_path || []
@fragment_context = fragment_context
@builder = prev_builder
@fragment_path = fragment_path

if found_block
@builder.item_context = found_item
set!(key, found_options, &found_block)
end
else
Expand Down
23 changes: 7 additions & 16 deletions lib/props_template/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@ class InvalidScopeForObjError < StandardError; end
class InvalidScopeForChildError < StandardError; end

class Base
attr_accessor :item_context

def initialize(encoder = nil)
@stream = Oj::StringWriter.new(mode: :rails)
@scope = nil
@item_context = nil
end

def set_content!(options = {})
@scope = nil
@item_context = nil
yield
if @scope.nil?
@stream.push_object
Expand Down Expand Up @@ -59,30 +63,17 @@ def set!(key, value = nil)
nil
end

def refine_item_options(item, options)
options
end

def handle_collection_item(collection, item, index, options)
@item_context = item

set_content!(options) do
yield
end
end

def refine_all_item_options(all_options)
all_options
end

def handle_collection(collection, options)
all_opts = collection.map do |item|
refine_item_options(item, options.clone)
end

all_opts = refine_all_item_options(all_opts)

collection.each_with_index do |item, index|
pass_opts = all_opts[index]
handle_collection_item(collection, item, index, pass_opts) do
handle_collection_item(collection, item, index, options) do
# todo: remove index?
yield item, index
end
Expand Down
53 changes: 25 additions & 28 deletions lib/props_template/base_with_extensions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def traveled_path!
def set_content!(options = {})
return super if !@em.has_extensions(options)

@em.handle(options) do
@em.handle(options, item_context) do
yield
end
end
Expand All @@ -51,10 +51,6 @@ def format_key(key)
end

def set!(key, options = {}, &block)
if block || options.is_a?(Props::Options)
options = @em.refine_options(options)
end

if !block && options.is_a?(Props::Options)
options.valid_for_set!
super {}
Expand All @@ -78,16 +74,37 @@ def handle_set_block(key, options)
nil
end

def handle_collection(collection, options)
if options[:cache]
key, rest = [*options[:cache]]

if ::Proc === key
cache_keys = collection.map do |item|
key.call(item)
end
@em.load_cache(cache_keys, rest || {})
end
end

super
end

def handle_collection_item(collection, item, index, options)
if !options[:key]
@traveled_path.push(index)
else
id, val = options[:key]
if (key = options[:key])
val = if item.respond_to? key
item.send(key)
elsif item.is_a? Hash
item[key] || item[key.to_sym]
end
end

if id.nil?
if key.nil?
@traveled_path.push(index)
else
@traveled_path.push("#{id}=#{val}")
@traveled_path.push("#{key}=#{val}")
end
end

Expand All @@ -96,25 +113,5 @@ def handle_collection_item(collection, item, index, options)
@traveled_path.pop
nil
end

def refine_all_item_options(all_options)
@em.refine_all_item_options(all_options)
end

def refine_item_options(item, options)
return options if options.empty?

if (key = options[:key])
val = if item.respond_to? key
item.send(key)
elsif item.is_a? Hash
item[key] || item[key.to_sym]
end

options[:key] = [options[:key], val]
end

@em.refine_options(options, item)
end
end
end
58 changes: 28 additions & 30 deletions lib/props_template/extension_manager.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
module Props
class ExtensionManager
attr_reader :base, :builder, :context
attr_reader :base, :builder, :context, :cache, :partialer

delegate :load_cache, to: :cache
delegate :find_and_add_template, to: :partialer

def initialize(base, defered = [], fragments = [])
@base = base
Expand All @@ -16,22 +19,6 @@ def disable_deferments
@deferment.disable!
end

def refine_options(options, item = nil)
options = @partialer.refine_options(options, item)
if !@deferment.disabled
options = @deferment.refine_options(options, item)
end

Cache.refine_options(options, item)
end

def refine_all_item_options(all_options)
return all_options if all_options.empty?

all_options = @partialer.find_and_add_template(all_options)
@cache.multi_fetch_and_add_results(all_options)
end

def deferred
@deferment.deferred
end
Expand All @@ -44,26 +31,35 @@ def has_extensions(options)
options[:defer] || options[:cache] || options[:partial] || options[:key]
end

def handle(options)
def handle(options, item_context = nil)
return yield if !has_extensions(options)

if options[:defer] && !@deferment.disabled
placeholder = @deferment.handle(options)
if (key = options[:key]) && item_context
val = if item_context.respond_to? key
item_context.send(key)
elsif item_context.is_a? Hash
item_context[key] || item_context[key.to_sym]
end
end

deferment_type = @deferment.extract_deferment_type(options, item_context) if !@deferment.disabled

if deferment_type
placeholder = @deferment.handle(options, deferment_type, key, val)
base.stream.push_value(placeholder)
@fragment.handle(options)
@fragment.handle(options, item_context)
else
handle_cache(options) do
handle_cache(options, item_context) do
base.set_content! do
if options[:partial]
@fragment.handle(options)
@partialer.handle(options)
@fragment.handle(options, item_context)
@partialer.handle(options, item_context)
else
yield
end

if options[:key]
id, val = options[:key]
base.set!(id, val)
if key && val
base.set!(key, val)
end
end
end
Expand All @@ -72,11 +68,13 @@ def handle(options)

private

def handle_cache(options)
def handle_cache(options, item)
if options[:cache]
recently_cached = false

state = @cache.cache(*options[:cache]) do
key, rest = Cache
.normalize_options(options[:cache], item)
state = @cache.cache(key, rest) do
recently_cached = true
result = nil
start = base.stream.to_s.length
Expand All @@ -93,7 +91,7 @@ def handle_cache(options)
result
end

meta, raw_json = state.split("\n")
meta, raw_json = state.split("\n", 2)
next_deferred, next_fragments = Oj.load(meta)
deferred.push(*next_deferred)
fragments.push(*next_fragments)
Expand Down
51 changes: 17 additions & 34 deletions lib/props_template/extensions/cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,22 @@ module Props
class Cache
delegate :controller, :safe_concat, to: :@context

def self.refine_options(options, item = nil)
return options if !options[:cache]
attr_reader :results

pass_opts = options.clone
key, rest = [*options[:cache]]
def initialize(context)
@context = context
@results = {}
end

def self.normalize_options(options, item = nil)
key, rest = [*options]
rest ||= {}

if item && ::Proc === key
key = key.call(item)
end

pass_opts[:cache] = [key, rest]
pass_opts
end

def initialize(context)
@context = context
[key, rest]
end

attr_reader :context
Expand Down Expand Up @@ -48,32 +47,17 @@ def multi_fetch(keys, options = {})

keys.each do |k|
ckey = key_to_ckey[k]
result[k] = read_caches[ckey]

if read_caches[ckey]
result[k] = read_caches[ckey]
end
end

result
end

def multi_fetch_and_add_results(all_options)
first_opts = all_options[0]

if first_opts[:cache] && controller.perform_caching
keys = all_options.map { |i| i[:cache][0] }
c_opts = first_opts[:cache][1]
result = multi_fetch(keys, c_opts)

all_options.map do |opts|
key = opts[:cache][0]

if result.key? key
opts[:cache][1][:result] = result[key]
end

opts
end
else
all_options
end
def load_cache(keys, options = {})
@results = results.merge multi_fetch(keys, options)
end

# Copied from jbuilder
Expand All @@ -90,10 +74,9 @@ def cache(key = nil, options = {})
end

def cache_fragment_for(key, options, &block)
key = cache_key(key, options)

return options[:result] if options[:result]
return results[key] if results[key]

key = cache_key(key, options)
read_fragment_cache(key, options) || write_fragment_cache(key, options, &block)
end

Expand Down
Loading