dry-view 0.1.1 → 0.2.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +15 -11
  3. data/CHANGELOG.md +23 -0
  4. data/Gemfile +6 -5
  5. data/README.md +8 -1
  6. data/benchmarks/templates/{button.erb → button.html.erb} +0 -0
  7. data/benchmarks/view.rb +3 -4
  8. data/bin/console +7 -0
  9. data/dry-view.gemspec +7 -6
  10. data/lib/dry/view/controller.rb +107 -0
  11. data/lib/dry/view/exposure.rb +61 -0
  12. data/lib/dry/view/exposures.rb +50 -0
  13. data/lib/dry/view/path.rb +40 -0
  14. data/lib/dry/view/renderer.rb +20 -28
  15. data/lib/dry/view/scope.rb +55 -0
  16. data/lib/dry/view/version.rb +1 -1
  17. data/lib/dry/view.rb +1 -1
  18. data/spec/fixtures/templates/empty.html.slim +1 -0
  19. data/spec/fixtures/templates/layouts/app.html.slim +1 -1
  20. data/spec/fixtures/templates/layouts/app.txt.erb +1 -1
  21. data/spec/fixtures/templates/parts_with_args/_box.html.slim +3 -0
  22. data/spec/fixtures/templates/parts_with_args.html.slim +3 -0
  23. data/spec/fixtures/templates/users/_tbody.html.slim +1 -1
  24. data/spec/fixtures/templates/users.html.slim +4 -4
  25. data/spec/fixtures/templates/users.txt.erb +0 -2
  26. data/spec/fixtures/templates/users_with_count.html.slim +5 -0
  27. data/spec/fixtures/templates_override/users.html.slim +5 -0
  28. data/spec/integration/exposures_spec.rb +178 -0
  29. data/spec/integration/view_spec.rb +83 -20
  30. data/spec/spec_helper.rb +13 -3
  31. data/spec/unit/controller_spec.rb +36 -0
  32. data/spec/unit/exposure_spec.rb +146 -0
  33. data/spec/unit/exposures_spec.rb +63 -0
  34. data/spec/unit/renderer_spec.rb +2 -1
  35. data/spec/unit/scope_spec.rb +98 -0
  36. metadata +36 -46
  37. data/lib/dry/view/layout.rb +0 -126
  38. data/lib/dry/view/null_part.rb +0 -30
  39. data/lib/dry/view/part.rb +0 -39
  40. data/lib/dry/view/value_part.rb +0 -50
  41. data/spec/unit/layout_spec.rb +0 -55
  42. data/spec/unit/null_part_spec.rb +0 -39
  43. data/spec/unit/value_part_spec.rb +0 -55
@@ -0,0 +1,98 @@
1
+ require 'dry/view/scope'
2
+
3
+ RSpec.describe Dry::View::Scope do
4
+ subject(:scope) {
5
+ described_class.new(renderer, data, context)
6
+ }
7
+
8
+ let(:renderer) { double("renderer") }
9
+ let(:data) { {} }
10
+ let(:context) { Object.new }
11
+
12
+ describe "missing method behavior" do
13
+ before do
14
+ allow(renderer).to receive(:lookup).and_return false
15
+ allow(renderer).to receive(:render)
16
+ end
17
+
18
+ describe "accessing data" do
19
+ let(:data) { {user_name: "Jane Doe", current_user: "data's current_user"} }
20
+ let(:context) {
21
+ Class.new do
22
+ def current_user
23
+ "context's current_user"
24
+ end
25
+ end.new
26
+ }
27
+
28
+ before do
29
+ allow(renderer).to receive(:lookup).with('_current_user').and_return '_current_user.html.slim'
30
+ end
31
+
32
+ it "returns matching scope data" do
33
+ expect(scope.user_name).to eq "Jane Doe"
34
+ end
35
+
36
+ it "raises an error when no data matches" do
37
+ expect { scope.missing }.to raise_error(NoMethodError)
38
+ end
39
+
40
+ it "returns data in favour of both context methods and partials" do
41
+ expect(scope.current_user).to eq "data's current_user"
42
+ end
43
+ end
44
+
45
+ describe "accessing context" do
46
+ let(:context) {
47
+ Class.new do
48
+ def current_user
49
+ "context's current_user"
50
+ end
51
+
52
+ def asset(name)
53
+ "#{name}.jpg"
54
+ end
55
+ end.new
56
+ }
57
+
58
+ before do
59
+ allow(renderer).to receive(:lookup).with('_current_user').and_return '_current_user.html.slim'
60
+ end
61
+
62
+ it "forwards to matching methods on the context in favour of partials" do
63
+ expect(scope.current_user).to eq "context's current_user"
64
+ end
65
+
66
+ it "allows arguments to be passed to those methods as normal" do
67
+ expect(scope.asset("mindblown")).to eq "mindblown.jpg"
68
+ end
69
+
70
+ it "raises an error when no method matches" do
71
+ expect { scope.missing }.to raise_error(NoMethodError)
72
+ end
73
+ end
74
+
75
+ describe "rendering" do
76
+ before do
77
+ allow(renderer).to receive(:lookup).with('_list').and_return '_list.html.slim'
78
+ end
79
+
80
+ it "renders a matching partial using the existing scope" do
81
+ scope.list
82
+
83
+ expect(renderer).to have_received(:render).with('_list.html.slim', scope)
84
+ end
85
+
86
+ it "renders a matching partial using a scope based on arguments passed" do
87
+ scope.list(something: 'else')
88
+
89
+ expect(renderer).to have_received(:render)
90
+ .with('_list.html.slim', described_class.new(renderer, something: 'else'))
91
+ end
92
+
93
+ it "raises an error if arguments passed are not a hash" do
94
+ expect { scope.list('hi') }.to raise_error(ArgumentError)
95
+ end
96
+ end
97
+ end
98
+ end
metadata CHANGED
@@ -1,29 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-view
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Solnica
8
+ - Tim Riley
8
9
  autorequire:
9
- bindir: bin
10
+ bindir: exe
10
11
  cert_chain: []
11
- date: 2016-07-07 00:00:00.000000000 Z
12
+ date: 2017-01-30 00:00:00.000000000 Z
12
13
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: inflecto
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
14
  - !ruby/object:Gem::Dependency
28
15
  name: tilt
29
16
  requirement: !ruby/object:Gem::Requirement
@@ -108,23 +95,10 @@ dependencies:
108
95
  - - "~>"
109
96
  - !ruby/object:Gem::Version
110
97
  version: '3.1'
111
- - !ruby/object:Gem::Dependency
112
- name: capybara
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: '2.5'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "~>"
123
- - !ruby/object:Gem::Version
124
- version: '2.5'
125
- description: Lightweight web application stack on top of Roda
98
+ description: Functional view rendering system
126
99
  email:
127
100
  - piotr.solnica@gmail.com
101
+ - tim@icelab.com.au
128
102
  executables: []
129
103
  extensions: []
130
104
  extra_rdoc_files: []
@@ -137,20 +111,25 @@ files:
137
111
  - LICENSE.md
138
112
  - README.md
139
113
  - Rakefile
140
- - benchmarks/templates/button.erb
114
+ - benchmarks/templates/button.html.erb
141
115
  - benchmarks/view.rb
116
+ - bin/console
142
117
  - dry-view.gemspec
143
118
  - lib/dry-view.rb
144
119
  - lib/dry/view.rb
145
- - lib/dry/view/layout.rb
146
- - lib/dry/view/null_part.rb
147
- - lib/dry/view/part.rb
120
+ - lib/dry/view/controller.rb
121
+ - lib/dry/view/exposure.rb
122
+ - lib/dry/view/exposures.rb
123
+ - lib/dry/view/path.rb
148
124
  - lib/dry/view/renderer.rb
149
- - lib/dry/view/value_part.rb
125
+ - lib/dry/view/scope.rb
150
126
  - lib/dry/view/version.rb
127
+ - spec/fixtures/templates/empty.html.slim
151
128
  - spec/fixtures/templates/hello.html.slim
152
129
  - spec/fixtures/templates/layouts/app.html.slim
153
130
  - spec/fixtures/templates/layouts/app.txt.erb
131
+ - spec/fixtures/templates/parts_with_args.html.slim
132
+ - spec/fixtures/templates/parts_with_args/_box.html.slim
154
133
  - spec/fixtures/templates/shared/_index_table.html.slim
155
134
  - spec/fixtures/templates/shared/_shared_hello.html.slim
156
135
  - spec/fixtures/templates/tasks.html.slim
@@ -159,12 +138,16 @@ files:
159
138
  - spec/fixtures/templates/users.txt.erb
160
139
  - spec/fixtures/templates/users/_row.html.slim
161
140
  - spec/fixtures/templates/users/_tbody.html.slim
141
+ - spec/fixtures/templates/users_with_count.html.slim
142
+ - spec/fixtures/templates_override/users.html.slim
143
+ - spec/integration/exposures_spec.rb
162
144
  - spec/integration/view_spec.rb
163
145
  - spec/spec_helper.rb
164
- - spec/unit/layout_spec.rb
165
- - spec/unit/null_part_spec.rb
146
+ - spec/unit/controller_spec.rb
147
+ - spec/unit/exposure_spec.rb
148
+ - spec/unit/exposures_spec.rb
166
149
  - spec/unit/renderer_spec.rb
167
- - spec/unit/value_part_spec.rb
150
+ - spec/unit/scope_spec.rb
168
151
  homepage: https://github.com/dry-rb/dry-view
169
152
  licenses:
170
153
  - MIT
@@ -177,7 +160,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
177
160
  requirements:
178
161
  - - ">="
179
162
  - !ruby/object:Gem::Version
180
- version: '0'
163
+ version: 2.1.0
181
164
  required_rubygems_version: !ruby/object:Gem::Requirement
182
165
  requirements:
183
166
  - - ">="
@@ -185,14 +168,17 @@ required_rubygems_version: !ruby/object:Gem::Requirement
185
168
  version: '0'
186
169
  requirements: []
187
170
  rubyforge_project:
188
- rubygems_version: 2.5.1
171
+ rubygems_version: 2.6.8
189
172
  signing_key:
190
173
  specification_version: 4
191
- summary: Lightweight web application stack on top of Roda
174
+ summary: Functional view rendering system
192
175
  test_files:
176
+ - spec/fixtures/templates/empty.html.slim
193
177
  - spec/fixtures/templates/hello.html.slim
194
178
  - spec/fixtures/templates/layouts/app.html.slim
195
179
  - spec/fixtures/templates/layouts/app.txt.erb
180
+ - spec/fixtures/templates/parts_with_args.html.slim
181
+ - spec/fixtures/templates/parts_with_args/_box.html.slim
196
182
  - spec/fixtures/templates/shared/_index_table.html.slim
197
183
  - spec/fixtures/templates/shared/_shared_hello.html.slim
198
184
  - spec/fixtures/templates/tasks.html.slim
@@ -201,9 +187,13 @@ test_files:
201
187
  - spec/fixtures/templates/users.txt.erb
202
188
  - spec/fixtures/templates/users/_row.html.slim
203
189
  - spec/fixtures/templates/users/_tbody.html.slim
190
+ - spec/fixtures/templates/users_with_count.html.slim
191
+ - spec/fixtures/templates_override/users.html.slim
192
+ - spec/integration/exposures_spec.rb
204
193
  - spec/integration/view_spec.rb
205
194
  - spec/spec_helper.rb
206
- - spec/unit/layout_spec.rb
207
- - spec/unit/null_part_spec.rb
195
+ - spec/unit/controller_spec.rb
196
+ - spec/unit/exposure_spec.rb
197
+ - spec/unit/exposures_spec.rb
208
198
  - spec/unit/renderer_spec.rb
209
- - spec/unit/value_part_spec.rb
199
+ - spec/unit/scope_spec.rb
@@ -1,126 +0,0 @@
1
- require 'dry-configurable'
2
- require 'dry-equalizer'
3
- require 'inflecto'
4
-
5
- require 'dry/view/part'
6
- require 'dry/view/value_part'
7
- require 'dry/view/null_part'
8
- require 'dry/view/renderer'
9
-
10
- module Dry
11
- module View
12
- class Layout
13
- include Dry::Equalizer(:config)
14
-
15
- DEFAULT_DIR = 'layouts'.freeze
16
-
17
- extend Dry::Configurable
18
-
19
- setting :root
20
- setting :name
21
- setting :template
22
- setting :formats, { html: :erb }
23
- setting :scope
24
-
25
- attr_reader :config, :scope, :layout_dir, :layout_path, :template_path,
26
- :default_format
27
-
28
- def self.renderer(format = default_format)
29
- unless config.formats.key?(format.to_sym)
30
- raise ArgumentError, "format +#{format}+ is not configured"
31
- end
32
-
33
- renderers[format]
34
- end
35
-
36
- def self.renderers
37
- @renderers ||= Hash.new do |h, key|
38
- h[key.to_sym] = Renderer.new(
39
- config.root, format: key, engine: config.formats[key.to_sym]
40
- )
41
- end
42
- end
43
-
44
- def self.default_format
45
- config.formats.keys.first
46
- end
47
-
48
- def initialize
49
- @config = self.class.config
50
- @default_format = self.class.default_format
51
- @layout_dir = DEFAULT_DIR
52
- @layout_path = "#{layout_dir}/#{config.name}"
53
- @template_path = config.template
54
- @scope = config.scope
55
- end
56
-
57
- def call(options = {})
58
- renderer = self.class.renderer(options.fetch(:format, default_format))
59
-
60
- template_content = renderer.(template_path, template_scope(options, renderer))
61
-
62
- renderer.(layout_path, layout_scope(options, renderer)) do
63
- template_content
64
- end
65
- end
66
-
67
- def locals(options)
68
- options.fetch(:locals, {})
69
- end
70
-
71
- def parts(locals, renderer)
72
- return empty_part(template_path, renderer) unless locals.any?
73
-
74
- part_hash = locals.each_with_object({}) do |(key, value), result|
75
- part =
76
- case value
77
- when Array
78
- el_key = Inflecto.singularize(key).to_sym
79
-
80
- template_part(
81
- key, renderer,
82
- value.map { |element| template_part(el_key, renderer, element) }
83
- )
84
- else
85
- template_part(key, renderer, value)
86
- end
87
-
88
- result[key] = part
89
- end
90
-
91
- part(template_path, renderer, part_hash)
92
- end
93
-
94
- private
95
-
96
- def layout_scope(options, renderer)
97
- part_hash = {
98
- page: layout_part(:page, renderer, options.fetch(:scope, scope))
99
- }
100
-
101
- part(layout_dir, renderer, part_hash)
102
- end
103
-
104
- def template_scope(options, renderer)
105
- parts(locals(options), renderer)
106
- end
107
-
108
- def layout_part(name, renderer, value)
109
- part(layout_dir, renderer, { name => value })
110
- end
111
-
112
- def template_part(name, renderer, value)
113
- part(template_path, renderer, { name => value })
114
- end
115
-
116
- def part(dir, renderer, value = {})
117
- part_class = value.values[0] ? ValuePart : NullPart
118
- part_class.new(renderer.chdir(dir), value)
119
- end
120
-
121
- def empty_part(dir, renderer)
122
- Part.new(renderer.chdir(dir))
123
- end
124
- end
125
- end
126
- end
@@ -1,30 +0,0 @@
1
- require 'dry-equalizer'
2
- require 'dry/view/value_part'
3
-
4
- module Dry
5
- module View
6
- class NullPart < ValuePart
7
- def [](key)
8
- end
9
-
10
- def each(&block)
11
- end
12
-
13
- def respond_to_missing?(*)
14
- true
15
- end
16
-
17
- private
18
-
19
- def method_missing(meth, *args, &block)
20
- template_path = template?("#{meth}_missing")
21
-
22
- if template_path
23
- render(template_path)
24
- else
25
- nil
26
- end
27
- end
28
- end
29
- end
30
- end
data/lib/dry/view/part.rb DELETED
@@ -1,39 +0,0 @@
1
- require 'dry-equalizer'
2
-
3
- module Dry
4
- module View
5
- class Part
6
- include Dry::Equalizer(:renderer)
7
-
8
- attr_reader :renderer
9
-
10
- def initialize(renderer)
11
- @renderer = renderer
12
- end
13
-
14
- def render(path, &block)
15
- renderer.render(path, self, &block)
16
- end
17
-
18
- def template?(name)
19
- renderer.lookup("_#{name}")
20
- end
21
-
22
- def respond_to_missing?(meth, include_private = false)
23
- super || template?(meth)
24
- end
25
-
26
- private
27
-
28
- def method_missing(meth, *args, &block)
29
- template_path = template?(meth)
30
-
31
- if template_path
32
- render(template_path, &block)
33
- else
34
- super
35
- end
36
- end
37
- end
38
- end
39
- end
@@ -1,50 +0,0 @@
1
- require 'dry-equalizer'
2
- require 'dry/view/part'
3
-
4
- module Dry
5
- module View
6
- class ValuePart < Part
7
- include Dry::Equalizer(:renderer, :_data, :_value)
8
-
9
- attr_reader :_data, :_value
10
-
11
- def initialize(renderer, data)
12
- super(renderer)
13
- @_data = data
14
- @_value = data.values[0]
15
- end
16
-
17
- def to_s
18
- _value.to_s
19
- end
20
-
21
- def [](key)
22
- _value[key]
23
- end
24
-
25
- def each(&block)
26
- _value.each(&block)
27
- end
28
-
29
- def respond_to_missing?(meth, include_private = false)
30
- _data.key?(meth) || super
31
- end
32
-
33
- private
34
-
35
- def method_missing(meth, *args, &block)
36
- template_path = template?(meth)
37
-
38
- if template_path
39
- render(template_path, &block)
40
- elsif _data.key?(meth)
41
- _data[meth]
42
- elsif _value.respond_to?(meth)
43
- _value.public_send(meth, *args, &block)
44
- else
45
- super
46
- end
47
- end
48
- end
49
- end
50
- end
@@ -1,55 +0,0 @@
1
- RSpec.describe Dry::View::Layout do
2
- subject(:layout) { layout_class.new }
3
-
4
- let(:layout_class) do
5
- klass = Class.new(Dry::View::Layout)
6
-
7
- klass.configure do |config|
8
- config.root = SPEC_ROOT.join('fixtures/templates')
9
- config.name = 'app'
10
- config.template = 'user'
11
- config.formats = {html: :slim}
12
- end
13
-
14
- klass
15
- end
16
-
17
- let(:page) do
18
- double(:page, title: 'Test')
19
- end
20
-
21
- let(:options) do
22
- { scope: page, locals: { user: { name: 'Jane' }, header: { title: 'User' } } }
23
- end
24
-
25
- let(:renderer) do
26
- layout.class.renderers[:html]
27
- end
28
-
29
- describe '#call' do
30
- it 'renders template within the layout' do
31
- expect(layout.(options)).to eql(
32
- '<!DOCTYPE html><html><head><title>Test</title></head><body><h1>User</h1><p>Jane</p></body></html>'
33
- )
34
- end
35
- end
36
-
37
- describe '#parts' do
38
- it 'returns view parts' do
39
- part = layout.parts({ user: { id: 1, name: 'Jane' } }, renderer)
40
-
41
- expect(part[:id]).to be(1)
42
- expect(part[:name]).to eql('Jane')
43
- end
44
-
45
- it 'builds null parts for nil values' do
46
- part = layout.parts({ user: nil }, renderer)
47
-
48
- expect(part[:id]).to be_nil
49
- end
50
-
51
- it 'returns empty part when no locals are passed' do
52
- expect(layout.parts({}, renderer)).to be_instance_of(Dry::View::Part)
53
- end
54
- end
55
- end
@@ -1,39 +0,0 @@
1
- require 'dry/view/null_part'
2
-
3
- RSpec.describe Dry::View::NullPart do
4
- subject(:part) do
5
- Dry::View::NullPart.new(renderer, data)
6
- end
7
-
8
- let(:name) { :user }
9
- let(:data) { { user: nil } }
10
-
11
- let(:renderer) { double(:renderer) }
12
-
13
- describe '#[]' do
14
- it 'returns nil for any data value names' do
15
- expect(part[:email]).to eql(nil)
16
- end
17
- end
18
-
19
- describe '#method_missing' do
20
- it 'renders a template with the _missing suffix' do
21
- expect(renderer).to receive(:lookup).with('_row_missing').and_return('_row_missing.slim')
22
- expect(renderer).to receive(:render).with('_row_missing.slim', part)
23
-
24
- part.row
25
- end
26
-
27
- it 'renders a _missing template within another when block is passed' do
28
- block = proc { part.fields }
29
-
30
- expect(renderer).to receive(:lookup).with('_form_missing').and_return('form_missing.slim')
31
- expect(renderer).to receive(:lookup).with('_fields_missing').and_return('fields_missing.slim')
32
-
33
- expect(renderer).to receive(:render).with('form_missing.slim', part, &block)
34
- expect(renderer).to receive(:render).with('fields_missing.slim', part)
35
-
36
- part.form(block)
37
- end
38
- end
39
- end
@@ -1,55 +0,0 @@
1
- require 'dry/view/part'
2
-
3
- RSpec.describe Dry::View::ValuePart do
4
- subject(:part) do
5
- Dry::View::ValuePart.new(renderer, data)
6
- end
7
-
8
- let(:name) { :user }
9
- let(:data) { { user: { email: 'jane@doe.org' } } }
10
-
11
- let(:renderer) { double(:renderer) }
12
-
13
- describe '#[]' do
14
- it 'gives access to data values' do
15
- expect(part[:email]).to eql('jane@doe.org')
16
- end
17
- end
18
-
19
- describe '#render' do
20
- it 'renders given template' do
21
- expect(renderer).to receive(:render).with('row.slim', part)
22
-
23
- part.render('row.slim')
24
- end
25
- end
26
-
27
- describe '#template?' do
28
- it 'asks renderer if there is a valid template for a given identifier' do
29
- expect(renderer).to receive(:lookup).with('_row').and_return('row.slim')
30
-
31
- expect(part.template?('row')).to eql('row.slim')
32
- end
33
- end
34
-
35
- describe '#method_missing' do
36
- it 'renders template' do
37
- expect(renderer).to receive(:lookup).with('_row').and_return('_row.slim')
38
- expect(renderer).to receive(:render).with('_row.slim', part)
39
-
40
- part.row
41
- end
42
-
43
- it 'renders template within another when block is passed' do
44
- block = proc { part.fields }
45
-
46
- expect(renderer).to receive(:lookup).with('_form').and_return('form.slim')
47
- expect(renderer).to receive(:lookup).with('_fields').and_return('fields.slim')
48
-
49
- expect(renderer).to receive(:render).with('form.slim', part, &block)
50
- expect(renderer).to receive(:render).with('fields.slim', part)
51
-
52
- part.form(block)
53
- end
54
- end
55
- end