dry-view 0.1.1 → 0.2.0

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