Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
timriley committed Dec 11, 2018
1 parent 4f71a6e commit 4edc05c
Show file tree
Hide file tree
Showing 20 changed files with 362 additions and 38 deletions.
20 changes: 16 additions & 4 deletions lib/dry/view/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
require_relative 'path'
require_relative 'rendered'
require_relative 'renderer'
require_relative 'scope'
require_relative 'scope_builder'

module Dry
module View
Expand All @@ -33,17 +33,23 @@ class Controller
end
setting :default_context, DEFAULT_CONTEXT

setting :scope

setting :inflector, Dry::Inflector.new

setting :part_builder, PartBuilder
setting :part_namespace

setting :scope_builder, ScopeBuilder
setting :scope_namespace

attr_reader :config
attr_reader :layout_dir
attr_reader :layout_path
attr_reader :template_path

attr_reader :part_builder
attr_reader :scope_builder

attr_reader :exposures

Expand Down Expand Up @@ -100,9 +106,14 @@ def initialize
@layout_path = "#{layout_dir}/#{config.layout}"
@template_path = config.template

@scope_builder = config.scope_builder.new(
namespace: config.scope_namespace,
inflector: config.inflector,
)
@part_builder = config.part_builder.new(
namespace: config.part_namespace,
inflector: config.inflector,
scope_builder: scope_builder,
)

@exposures = self.class.exposures.bind(self)
Expand Down Expand Up @@ -157,10 +168,11 @@ def template_scope(renderer, context, locals)
end

def scope(renderer, context, locals = EMPTY_LOCALS)
Scope.new(
renderer: renderer,
context: context,
scope_builder.(
name: config.scope,
locals: locals,
context: context,
renderer: renderer,
)
end

Expand Down
21 changes: 17 additions & 4 deletions lib/dry/view/part.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
require 'dry-equalizer'
require 'dry/view/scope'
require 'dry/view/missing_renderer'

module Dry
Expand All @@ -8,10 +7,11 @@ class Part
CONVENIENCE_METHODS = %i[
context
render
scope
value
].freeze

include Dry::Equalizer(:_name, :_value, :_part_builder, :_context, :_renderer)
include Dry::Equalizer(:_name, :_value, :_context, :_renderer, :_part_builder, :_scope_builder)

attr_reader :_name

Expand All @@ -23,6 +23,8 @@ class Part

attr_reader :_part_builder

attr_reader :_scope_builder

attr_reader :_decorated_attributes

# @api public
Expand All @@ -37,19 +39,29 @@ def self.decorated_attributes
@decorated_attributes ||= {}
end

def initialize(name:, value:, part_builder: Dry::View::PartBuilder.new, renderer: MissingRenderer.new, context: nil)
def initialize(name:, value:, context: nil, renderer: MissingRenderer.new, part_builder:, scope_builder:)
@_name = name
@_value = value
@_context = context
@_renderer = renderer
@_part_builder = part_builder
@_scope_builder = scope_builder
@_decorated_attributes = {}
end

def _render(partial_name, as: _name, **locals, &block)
_renderer.partial(partial_name, _render_scope(as, locals), &block)
end

def _scope(name = nil, **locals)
_scope_builder.(
name: name,
locals: locals,
context: _context,
renderer: _renderer,
)
end

def to_s
_value.to_s
end
Expand All @@ -61,6 +73,7 @@ def new(klass = (self.class), name: (_name), value: (_value), **options)
context: _context,
renderer: _renderer,
part_builder: _part_builder,
scope_builder: _scope_builder,
**options,
)
end
Expand All @@ -86,7 +99,7 @@ def respond_to_missing?(name, include_private = false)
end

def _render_scope(name, **locals)
Scope.new(
_scope_builder.(
locals: locals.merge(name => self),
context: _context,
renderer: _renderer,
Expand Down
13 changes: 11 additions & 2 deletions lib/dry/view/part_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ module View
class PartBuilder
attr_reader :namespace
attr_reader :inflector
attr_reader :scope_builder

def initialize(namespace: nil, inflector: Dry::Inflector.new)
def initialize(namespace: nil, inflector: Dry::Inflector.new, scope_builder:)
@namespace = namespace
@inflector = inflector
@scope_builder = scope_builder
end

def call(name:, value:, renderer:, context:, **options)
Expand All @@ -23,7 +25,14 @@ def call(name:, value:, renderer:, context:, **options)
def build_part(name:, value:, renderer:, context:, **options)
klass = part_class(name: name, **options)

klass.new(name: name, value: value, part_builder: self, renderer: renderer, context: context)
klass.new(
name: name,
value: value,
context: context,
renderer: renderer,
part_builder: self,
scope_builder: scope_builder,
)
end

def build_collection_part(name:, value:, renderer:, context:, **options)
Expand Down
41 changes: 32 additions & 9 deletions lib/dry/view/scope.rb
Original file line number Diff line number Diff line change
@@ -1,22 +1,40 @@
require 'dry-equalizer'
require 'dry/equalizer'
require 'dry/core/constants'

module Dry
module View
class Scope
include Dry::Equalizer(:_locals, :_context, :_renderer)
include Dry::Equalizer(:_name, :_locals, :_context, :_renderer, :_scope_builder)

attr_reader :_name
attr_reader :_locals
attr_reader :_context
attr_reader :_renderer
attr_reader :_scope_builder

def initialize(renderer:, context: nil, locals: {})
def initialize(name: nil, locals: Dry::Core::Constants::EMPTY_HASH, context: nil, renderer:, scope_builder:)
@_name = name
@_locals = locals
@_context = context
@_renderer = renderer
@_scope_builder = scope_builder
end

def render(partial_name, **locals, &block)
_renderer.partial(partial_name, __render_scope(locals), &block)
def render(partial_name = nil, **locals, &block)
partial_name ||= _name

raise ArgumentError, "+partial_name+ must be provided for unnamed scopes" unless partial_name

_renderer.partial(partial_name, _render_scope(locals), &block)
end

def scope(name = nil, **locals)
_scope_builder.(
name: name,
locals: locals,
context: _context,
renderer: _renderer,
)
end

private
Expand All @@ -31,11 +49,16 @@ def method_missing(name, *args, &block)
end
end

def __render_scope(**locals)
if locals.any?
self.class.new(renderer: _renderer, context: _context, locals: locals)
else
def _render_scope(**locals)
if locals.none?
self
else
self.class.new(
locals: locals,
context: _context,
renderer: _renderer,
scope_builder: _scope_builder,
)
end
end
end
Expand Down
62 changes: 62 additions & 0 deletions lib/dry/view/scope_builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
require 'dry/inflector'
require_relative 'scope'

module Dry
module View
class ScopeBuilder
attr_reader :namespace
attr_reader :inflector

def initialize(namespace: nil, inflector: Dry::Inflector.new)
@namespace = namespace
@inflector = inflector
end

def call(name: nil, locals:, context:, renderer:)
scope_class(name: name).new(
name: name,
locals: locals,
context: context,
renderer: renderer,
scope_builder: self,
)
end

private

DEFAULT_SCOPE_CLASS = Scope

def scope_class(name: nil)
if name.nil?
DEFAULT_SCOPE_CLASS
elsif name.is_a?(Class)
name
else
resolve_scope_class(name: name)
end
end

def resolve_scope_class(name:)
return fallback_class unless namespace

name = inflector.camelize(name.to_s)

# Give autoloaders a change to act
begin
klass = namespace.const_get(name)
rescue NameError
end

if !klass && namespace.const_defined?(name, false)
klass = namespace.const_get(name)
end

if klass && klass < Scope
klass
else
DEFAULT_SCOPE_CLASS
end
end
end
end
end
1 change: 1 addition & 0 deletions spec/fixtures/integration/scopes/anonymous_scope.html.slim
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
== scope(greeting: text).render(:greeting)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
== hello
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
== scope(:greeting).render
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
== scope(:greeting, greeting: text).render(:holler)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
== scope(:greeting, greeting: text).render
1 change: 1 addition & 0 deletions spec/fixtures/integration/scopes/scope_from_part.html.slim
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
== message.greeting
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
| Greeting: #{greeting}
1 change: 1 addition & 0 deletions spec/fixtures/integration/scopes/shared/_holler.html.slim
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
| Holler: #{greeting}
1 change: 0 additions & 1 deletion spec/integration/controller/locals_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
require "dry/view/part"

RSpec.describe "locals" do

specify "locals are decorated with parts by default" do
vc = Class.new(Dry::View::Controller) do
configure do |config|
Expand Down
11 changes: 10 additions & 1 deletion spec/integration/part/decorated_attributes_spec.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'dry/core/inflector'
require 'dry/view/scope_builder'

RSpec.describe 'Part / Decorated attributes' do
let(:article_class) {
Expand Down Expand Up @@ -53,9 +54,14 @@ def initialize(author:, body:)
article_part_class.new(
name: :article,
value: article,
part_builder: part_builder,
scope_builder: scope_builder,
)
}

let(:part_builder) { Dry::View::PartBuilder.new(scope_builder: scope_builder) }
let(:scope_builder) { Dry::View::ScopeBuilder.new }

describe 'decorating without options' do
describe 'multiple declarations' do
let(:article_part_class) {
Expand Down Expand Up @@ -147,6 +153,7 @@ class CommentPart < Dry::View::Part
name: :article,
value: article,
part_builder: part_builder,
scope_builder: scope_builder,
)
}

Expand All @@ -161,9 +168,11 @@ def part_class(name:, **options)
super
end
end
end.new
end.new(scope_builder: scope_builder)
}

let(:scope_builder) { Dry::View::ScopeBuilder.new }

before do
module Test
class AuthorPart < Dry::View::Part
Expand Down
Loading

0 comments on commit 4edc05c

Please sign in to comment.