dry-view 0.5.1 → 0.7.1

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +143 -18
  3. data/LICENSE +20 -0
  4. data/README.md +22 -14
  5. data/dry-view.gemspec +29 -21
  6. data/lib/dry-view.rb +3 -1
  7. data/lib/dry/view.rb +514 -2
  8. data/lib/dry/view/context.rb +80 -0
  9. data/lib/dry/view/decorated_attributes.rb +82 -0
  10. data/lib/dry/view/errors.rb +29 -0
  11. data/lib/dry/view/exposure.rb +35 -14
  12. data/lib/dry/view/exposures.rb +18 -6
  13. data/lib/dry/view/part.rb +166 -53
  14. data/lib/dry/view/part_builder.rb +140 -0
  15. data/lib/dry/view/path.rb +35 -7
  16. data/lib/dry/view/render_environment.rb +62 -0
  17. data/lib/dry/view/render_environment_missing.rb +44 -0
  18. data/lib/dry/view/rendered.rb +55 -0
  19. data/lib/dry/view/renderer.rb +36 -29
  20. data/lib/dry/view/scope.rb +160 -14
  21. data/lib/dry/view/scope_builder.rb +98 -0
  22. data/lib/dry/view/tilt.rb +78 -0
  23. data/lib/dry/view/tilt/erb.rb +26 -0
  24. data/lib/dry/view/tilt/erbse.rb +21 -0
  25. data/lib/dry/view/tilt/haml.rb +26 -0
  26. data/lib/dry/view/version.rb +5 -2
  27. metadata +78 -115
  28. data/.gitignore +0 -26
  29. data/.rspec +0 -2
  30. data/.travis.yml +0 -23
  31. data/CONTRIBUTING.md +0 -29
  32. data/Gemfile +0 -22
  33. data/LICENSE.md +0 -10
  34. data/Rakefile +0 -6
  35. data/benchmarks/templates/button.html.erb +0 -1
  36. data/benchmarks/view.rb +0 -24
  37. data/bin/console +0 -7
  38. data/lib/dry/view/controller.rb +0 -155
  39. data/lib/dry/view/decorator.rb +0 -45
  40. data/lib/dry/view/missing_renderer.rb +0 -15
  41. data/spec/fixtures/templates/_hello.html.slim +0 -1
  42. data/spec/fixtures/templates/decorated_parts.html.slim +0 -4
  43. data/spec/fixtures/templates/edit.html.slim +0 -11
  44. data/spec/fixtures/templates/empty.html.slim +0 -1
  45. data/spec/fixtures/templates/greeting.html.slim +0 -2
  46. data/spec/fixtures/templates/hello.html.slim +0 -1
  47. data/spec/fixtures/templates/layouts/app.html.slim +0 -6
  48. data/spec/fixtures/templates/layouts/app.txt.erb +0 -3
  49. data/spec/fixtures/templates/parts_with_args.html.slim +0 -3
  50. data/spec/fixtures/templates/parts_with_args/_box.html.slim +0 -3
  51. data/spec/fixtures/templates/shared/_index_table.html.slim +0 -2
  52. data/spec/fixtures/templates/shared/_shared_hello.html.slim +0 -1
  53. data/spec/fixtures/templates/tasks.html.slim +0 -3
  54. data/spec/fixtures/templates/user.html.slim +0 -2
  55. data/spec/fixtures/templates/users.html.slim +0 -5
  56. data/spec/fixtures/templates/users.txt.erb +0 -3
  57. data/spec/fixtures/templates/users/_row.html.slim +0 -2
  58. data/spec/fixtures/templates/users/_tbody.html.slim +0 -5
  59. data/spec/fixtures/templates/users_with_count.html.slim +0 -5
  60. data/spec/fixtures/templates/users_with_count_inherit.html.slim +0 -6
  61. data/spec/fixtures/templates_override/_hello.html.slim +0 -1
  62. data/spec/fixtures/templates_override/users.html.slim +0 -5
  63. data/spec/integration/decorator_spec.rb +0 -80
  64. data/spec/integration/exposures_spec.rb +0 -392
  65. data/spec/integration/part/decorated_attributes_spec.rb +0 -157
  66. data/spec/integration/view_spec.rb +0 -133
  67. data/spec/spec_helper.rb +0 -46
  68. data/spec/unit/controller_spec.rb +0 -37
  69. data/spec/unit/decorator_spec.rb +0 -61
  70. data/spec/unit/exposure_spec.rb +0 -227
  71. data/spec/unit/exposures_spec.rb +0 -103
  72. data/spec/unit/part_spec.rb +0 -90
  73. data/spec/unit/renderer_spec.rb +0 -57
  74. data/spec/unit/scope_spec.rb +0 -53
@@ -1,133 +0,0 @@
1
- RSpec.describe 'dry-view' do
2
- let(:vc_class) do
3
- Class.new(Dry::View::Controller) do
4
- configure do |config|
5
- config.paths = SPEC_ROOT.join('fixtures/templates')
6
- config.layout = 'app'
7
- config.template = 'users'
8
- config.default_format = :html
9
- end
10
- end
11
- end
12
-
13
- let(:context) do
14
- Struct.new(:title, :assets).new('dry-view rocks!', -> input { "#{input}.jpg" })
15
- end
16
-
17
- it 'renders within a layout and makes the provided context available everywhere' do
18
- vc = vc_class.new
19
-
20
- users = [
21
- { name: 'Jane', email: 'jane@doe.org' },
22
- { name: 'Joe', email: 'joe@doe.org' }
23
- ]
24
-
25
- expect(vc.(context: context, locals: {users: users})).to eql(
26
- '<!DOCTYPE html><html><head><title>dry-view rocks!</title></head><body><div class="users"><table><tbody><tr><td>Jane</td><td>jane@doe.org</td></tr><tr><td>Joe</td><td>joe@doe.org</td></tr></tbody></table></div><img src="mindblown.jpg" /></body></html>'
27
- )
28
- end
29
-
30
- it 'renders without a layout' do
31
- vc = Class.new(vc_class) do
32
- configure do |config|
33
- config.layout = false
34
- end
35
- end.new
36
-
37
- users = [
38
- { name: 'Jane', email: 'jane@doe.org' },
39
- { name: 'Joe', email: 'joe@doe.org' }
40
- ]
41
-
42
- expect(vc.(context: context, locals: {users: users})).to eql(
43
- '<div class="users"><table><tbody><tr><td>Jane</td><td>jane@doe.org</td></tr><tr><td>Joe</td><td>joe@doe.org</td></tr></tbody></table></div><img src="mindblown.jpg" />'
44
- )
45
- end
46
-
47
- it 'renders a view without locals' do
48
- vc = Class.new(vc_class) do
49
- configure do |config|
50
- config.template = 'empty'
51
- end
52
- end.new
53
-
54
- expect(vc.(context: context, locals: {})).to eq(
55
- '<!DOCTYPE html><html><head><title>dry-view rocks!</title></head><body><p>This is a view with no locals.</p></body></html>'
56
- )
57
- end
58
-
59
- it 'renders a view with an alternative format and engine' do
60
- vc = vc_class.new
61
-
62
- users = [
63
- { name: 'Jane', email: 'jane@doe.org' },
64
- { name: 'Joe', email: 'joe@doe.org' }
65
- ]
66
-
67
- expect(vc.(context: context, locals: {users: users}, format: 'txt').strip).to eql(
68
- "# dry-view rocks!\n\n* Jane (jane@doe.org)\n* Joe (joe@doe.org)"
69
- )
70
- end
71
-
72
- it 'renders a view with a template on another view path' do
73
- vc = Class.new(vc_class) do
74
- configure do |config|
75
- config.paths = [SPEC_ROOT.join('fixtures/templates_override')] + Array(config.paths)
76
- end
77
- end.new
78
-
79
- users = [
80
- { name: 'Jane', email: 'jane@doe.org' },
81
- { name: 'Joe', email: 'joe@doe.org' }
82
- ]
83
-
84
- expect(vc.(context: context, locals: {users: users})).to eq(
85
- '<!DOCTYPE html><html><head><title>dry-view rocks!</title></head><body><h1>OVERRIDE</h1><div class="users"><table><tbody><tr><td>Jane</td><td>jane@doe.org</td></tr><tr><td>Joe</td><td>joe@doe.org</td></tr></tbody></table></div></body></html>'
86
- )
87
- end
88
-
89
- it 'renders a view that passes arguments to partials' do
90
- vc = Class.new(vc_class) do
91
- configure do |config|
92
- config.template = 'parts_with_args'
93
- end
94
- end.new
95
-
96
- users = [
97
- { name: 'Jane', email: 'jane@doe.org' },
98
- { name: 'Joe', email: 'joe@doe.org' }
99
- ]
100
-
101
- expect(vc.(context: context, locals: {users: users})).to eq(
102
- '<!DOCTYPE html><html><head><title>dry-view rocks!</title></head><body><div class="users"><div class="box"><h2>Nombre</h2>Jane</div><div class="box"><h2>Nombre</h2>Joe</div></div></body></html>'
103
- )
104
- end
105
-
106
- describe 'inheritance' do
107
- let(:parent_view) do
108
- klass = Class.new(Dry::View::Controller)
109
-
110
- klass.setting :paths, SPEC_ROOT.join('fixtures/templates')
111
- klass.setting :layout, 'app'
112
- klass.setting :formats, {html: :slim}
113
-
114
- klass
115
- end
116
-
117
- let(:child_view) do
118
- Class.new(parent_view) do
119
- configure do |config|
120
- config.template = 'tasks'
121
- end
122
- end
123
- end
124
-
125
- it 'renders within a parent class layout using provided context' do
126
- vc = child_view.new
127
-
128
- expect(vc.(context: context, locals: { tasks: [{ title: 'one' }, { title: 'two' }] })).to eql(
129
- '<!DOCTYPE html><html><head><title>dry-view rocks!</title></head><body><ol><li>one</li><li>two</li></ol></body></html>'
130
- )
131
- end
132
- end
133
- end
data/spec/spec_helper.rb DELETED
@@ -1,46 +0,0 @@
1
- if RUBY_ENGINE == 'ruby'
2
- require 'simplecov'
3
- SimpleCov.start do
4
- add_filter "/spec/"
5
- end
6
- end
7
-
8
- begin
9
- require 'pry-byebug'
10
- rescue LoadError; end
11
-
12
- SPEC_ROOT = Pathname(__FILE__).dirname
13
-
14
- require 'erb'
15
- require 'slim'
16
-
17
- # Prefer plain ERB processor rather than erubis (which has problems on JRuby)
18
- require 'tilt'
19
- Tilt.register 'erb', Tilt::ERBTemplate
20
-
21
- require 'dry-view'
22
-
23
- module Test
24
- def self.remove_constants
25
- constants.each(&method(:remove_const))
26
- end
27
- end
28
-
29
- RSpec.configure do |config|
30
- config.disable_monkey_patching!
31
-
32
- config.order = :random
33
- Kernel.srand config.seed
34
-
35
- config.after do
36
- Test.remove_constants
37
- end
38
- end
39
-
40
- RSpec::Matchers.define :part_including do |data|
41
- match { |actual|
42
- data.all? { |(key, val)|
43
- actual._data[key] == val
44
- }
45
- }
46
- end
@@ -1,37 +0,0 @@
1
- RSpec.describe Dry::View::Controller do
2
- subject(:controller) {
3
- Class.new(Dry::View::Controller) do
4
- configure do |config|
5
- config.paths = SPEC_ROOT.join('fixtures/templates')
6
- config.layout = 'app'
7
- config.template = 'user'
8
- end
9
- end.new
10
- }
11
-
12
- let(:page) do
13
- double(:page, title: 'Test')
14
- end
15
-
16
- let(:options) do
17
- { context: page, locals: { user: { name: 'Jane' }, header: { title: 'User' } } }
18
- end
19
-
20
- describe '#call' do
21
- it 'renders template within the layout' do
22
- expect(controller.(options)).to eql(
23
- '<!DOCTYPE html><html><head><title>Test</title></head><body><h1>User</h1><p>Jane</p></body></html>'
24
- )
25
- end
26
-
27
- it 'provides a meaningful error if the template name is missing' do
28
- controller = Class.new(Dry::View::Controller) do
29
- configure do |config|
30
- config.paths = SPEC_ROOT.join('fixtures/templates')
31
- end
32
- end.new
33
-
34
- expect { controller.(options) }.to raise_error Dry::View::Controller::UndefinedTemplateError
35
- end
36
- end
37
- end
@@ -1,61 +0,0 @@
1
- RSpec.describe Dry::View::Decorator do
2
- subject(:decorator) { described_class.new }
3
-
4
- describe '#call' do
5
- let(:value) { double('value') }
6
- let(:renderer) { double('renderer') }
7
- let(:context) { double('context') }
8
- let(:options) { {} }
9
-
10
- describe 'returning a part value' do
11
- subject(:part) { decorator.('user', value, renderer: renderer, context: context, **options) }
12
-
13
- context 'no options provided' do
14
- it 'returns a Part' do
15
- expect(part).to be_a Dry::View::Part
16
- end
17
-
18
- it 'wraps the value' do
19
- expect(part._value).to eq value
20
- end
21
- end
22
-
23
- context 'part class provided via `:as` option' do
24
- let(:options) { {as: Test::CustomPart} }
25
-
26
- before do
27
- module Test
28
- CustomPart = Class.new(Dry::View::Part)
29
- end
30
- end
31
-
32
- it 'returns an instance of the provided class' do
33
- expect(part).to be_a Test::CustomPart
34
- end
35
-
36
- it 'wraps the value' do
37
- expect(part._value).to eq value
38
- end
39
- end
40
-
41
- context 'value is an array' do
42
- let(:child_a) { double('child a') }
43
- let(:child_b) { double('child a') }
44
- let(:value) { [child_a, child_b] }
45
-
46
- it 'returns a part wrapping the array' do
47
- expect(part).to be_a Dry::View::Part
48
- expect(part._value).to be_an Array
49
- end
50
-
51
- it 'wraps the elements within the array' do
52
- expect(part[0]).to be_a Dry::View::Part
53
- expect(part[0]._value).to eq child_a
54
-
55
- expect(part[1]).to be_a Dry::View::Part
56
- expect(part[1]._value).to eq child_b
57
- end
58
- end
59
- end
60
- end
61
- end
@@ -1,227 +0,0 @@
1
- RSpec.describe Dry::View::Exposure do
2
- subject(:exposure) { described_class.new(:hello, proc, object, **options) }
3
-
4
- let(:proc) { -> input { "hi" } }
5
- let(:object) { nil }
6
- let(:options) { {} }
7
-
8
- describe "initialization and attributes" do
9
- describe "#name" do
10
- it "accepts a name" do
11
- expect(exposure.name).to eql :hello
12
- end
13
- end
14
-
15
- describe "#proc" do
16
- it "accepts a proc" do
17
- expect(exposure.proc).to eql proc
18
- end
19
-
20
- it "allows a nil proc" do
21
- expect(described_class.new(:hello).proc).to be_nil
22
- end
23
- end
24
-
25
- describe "#object" do
26
- let(:object) { Object.new }
27
-
28
- it "accepts an object" do
29
- expect(exposure.object).to eq object
30
- end
31
-
32
- it "allows a nil object" do
33
- expect(described_class.new(:hello).object).to be_nil
34
- end
35
- end
36
-
37
- describe "#private?" do
38
- it "is false by default" do
39
- expect(exposure).not_to be_private
40
- end
41
-
42
- it "can be set on initialization" do
43
- expect(described_class.new(:hello, private: true)).to be_private
44
- end
45
- end
46
-
47
- describe "#default_value" do
48
- it "is nil by default" do
49
- expect(exposure.default_value).to be_nil
50
- end
51
-
52
- it "can be set on initialization" do
53
- exposuse = described_class.new(:hello, default: 'Hi !')
54
- expect(exposuse.default_value).to eq('Hi !')
55
- end
56
- end
57
- end
58
-
59
- describe "#bind" do
60
- subject(:bound_exposure) { exposure.bind(bind_object) }
61
-
62
- let(:bind_object) { Object.new }
63
-
64
- it "returns a new object" do
65
- expect(bound_exposure).not_to eql exposure
66
- end
67
-
68
- it "retains the bind object" do
69
- expect(bound_exposure.object).to eq bind_object
70
- end
71
-
72
- context "proc is set" do
73
- it "retains the existing proc" do
74
- expect(bound_exposure.proc).to eql proc
75
- end
76
- end
77
-
78
- context "proc is nil" do
79
- let(:proc) { nil }
80
-
81
- context "matching instance method" do
82
- let(:bind_object) do
83
- Class.new do
84
- def hello(input)
85
- "hi there, #{input.fetch(:name)}"
86
- end
87
- end.new
88
- end
89
-
90
- it "sets the proc to the method on the object matching the exposure's name" do
91
- expect(bound_exposure.proc).to eql bind_object.method(:hello)
92
- end
93
- end
94
-
95
- context "no matching instance method" do
96
- let(:object) { Object.new }
97
-
98
- it "leaves proc as nil" do
99
- expect(bound_exposure.proc).to be_nil
100
- end
101
- end
102
- end
103
- end
104
-
105
- describe "#dependency_names" do
106
- context "proc provided" do
107
- let(:proc) { -> input, foo, bar { "hi" } }
108
-
109
- it "returns an array of exposure dependencies derived from the proc's argument names" do
110
- expect(exposure.dependency_names).to eql [:input, :foo, :bar]
111
- end
112
- end
113
-
114
- context "matching instance method" do
115
- let(:proc) { nil }
116
-
117
- let(:object) do
118
- Class.new do
119
- def hello(input, bar, baz)
120
- "hi there, #{input.fetch(:name)}"
121
- end
122
- end.new
123
- end
124
-
125
- it "returns an array of exposure dependencies derived from the instance method's argument names" do
126
- expect(exposure.dependency_names).to eql [:input, :bar, :baz]
127
- end
128
- end
129
-
130
- context "proc is nil" do
131
- let(:proc) { nil }
132
-
133
- it "returns no dependencies" do
134
- expect(exposure.dependency_names).to eql []
135
- end
136
- end
137
- end
138
-
139
- describe "#call" do
140
- let(:input) { {name: "Jane"} }
141
-
142
- context "proc expects input only" do
143
- let(:proc) { -> name: { name } }
144
-
145
- it "sends the input to the proc" do
146
- expect(exposure.(input)).to eql "Jane"
147
- end
148
- end
149
-
150
- context "proc expects input and dependencies" do
151
- let(:proc) { -> greeting, name: { "#{greeting}, #{name}" } }
152
- let(:locals) { {greeting: "Hola"} }
153
-
154
- it "sends the input and dependency values to the proc" do
155
- expect(exposure.(input, locals)).to eq "Hola, Jane"
156
- end
157
- end
158
-
159
- context "Default value" do
160
- let(:options) { { default: "John" } }
161
-
162
- context "use default value" do
163
- let(:proc) { nil }
164
-
165
- it "use the default value" do
166
- expect(exposure.({})).to eq "John"
167
- end
168
- end
169
-
170
- context "use input value instead of default" do
171
- let(:proc) { nil }
172
-
173
- it "use the default value" do
174
- expect(exposure.({hello: "Jane"})).to eq "Jane"
175
- end
176
- end
177
-
178
- context "use input value over default even when input is nil" do
179
- let(:proc) { nil }
180
-
181
- it "use the default value" do
182
- expect(exposure.({hello: nil})).to eq nil
183
- end
184
- end
185
- end
186
-
187
- context "proc expects dependencies only" do
188
- let(:proc) { -> greeting, farewell { "#{greeting}, #{farewell}" } }
189
- let(:locals) { {greeting: "Hola", farewell: "Adios"} }
190
-
191
- it "sends the dependency values to the proc" do
192
- expect(exposure.(input, locals)).to eq "Hola, Adios"
193
- end
194
- end
195
-
196
- context "proc accesses object instance" do
197
- let(:proc) { -> name: { "My name is #{name} but call me #{title} #{name}" } }
198
-
199
- let(:object) do
200
- Class.new do
201
- attr_reader :title
202
-
203
- def initialize(title)
204
- @title = title
205
- end
206
- end.new("Dr")
207
- end
208
-
209
- it "makes the instance available as self" do
210
- expect(exposure.(input)).to eq "My name is Jane but call me Dr Jane"
211
- end
212
- end
213
-
214
- context "no proc" do
215
- let(:proc) { nil }
216
- let(:input) { {hello: "hi there"} }
217
-
218
- it "returns a matching key from the input" do
219
- expect(exposure.(input)).to eq "hi there"
220
- end
221
-
222
- it "returns nil when no input key matches" do
223
- expect(exposure.(nothing_matches_here: true)).to be_nil
224
- end
225
- end
226
- end
227
- end