rocketio-views 0.4.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.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/README.md +3 -0
- data/Rakefile +8 -0
- data/lib/rocketio-views.rb +50 -0
- data/lib/rocketio-views/controller.rb +170 -0
- data/lib/rocketio-views/engine.rb +76 -0
- data/lib/rocketio-views/layout.rb +27 -0
- data/lib/rocketio-views/layouts.rb +85 -0
- data/lib/rocketio-views/template_vars.rb +34 -0
- data/lib/rocketio-views/templates.rb +83 -0
- data/lib/rocketio-views/version.rb +5 -0
- data/rocketio.gemspec +28 -0
- data/test/b.erb +1 -0
- data/test/c.erb +1 -0
- data/test/engine_test.rb +71 -0
- data/test/index.erb +1 -0
- data/test/items.erb +1 -0
- data/test/layout.erb +1 -0
- data/test/layout_test.rb +123 -0
- data/test/layouts/master.erb +1 -0
- data/test/layouts_test.rb +145 -0
- data/test/master.erb +1 -0
- data/test/render_test.rb +104 -0
- data/test/setup.rb +32 -0
- data/test/template_vars_test.rb +106 -0
- data/test/templates/a/get.erb +1 -0
- data/test/templates/master.erb +1 -0
- data/test/templates_test.rb +146 -0
- metadata +187 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
module RocketIO
|
2
|
+
class Controller
|
3
|
+
|
4
|
+
# define variables to be used on templates alongside provided locals.
|
5
|
+
#
|
6
|
+
# @param name
|
7
|
+
# @param value
|
8
|
+
#
|
9
|
+
def self.define_template_var name, value = nil, &block
|
10
|
+
value || block || raise(ArgumentError, 'A value or a block expected')
|
11
|
+
(@__template_vars__ ||= {})[name.to_sym] = (block || value).freeze
|
12
|
+
define_template_vars_methods
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.define_template_vars_methods source = self
|
16
|
+
return unless source.instance_variables.include?(:@__template_vars__)
|
17
|
+
vars = source.instance_variable_get(:@__template_vars__).each_with_object(allocate.__template_vars__.dup) do |(name,value),o|
|
18
|
+
o[name] = :"__#{name}_template_var__"
|
19
|
+
if value.is_a?(Proc)
|
20
|
+
api.delete define_method(o[name], &value)
|
21
|
+
else
|
22
|
+
api.delete define_method(o[name]) {value}
|
23
|
+
end
|
24
|
+
end.freeze
|
25
|
+
api.delete define_method(:__template_vars__) {vars}
|
26
|
+
end
|
27
|
+
|
28
|
+
def __template_vars__; RocketIO::EMPTY_HASH end
|
29
|
+
|
30
|
+
def template_vars
|
31
|
+
__template_vars__.each_with_object({}) {|(k,v),o| o[k] = __send__(v)}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module RocketIO
|
2
|
+
class Controller
|
3
|
+
|
4
|
+
# if only name given it will search for a file with same name in controller's dirname
|
5
|
+
#
|
6
|
+
# if file name differs from template name pass it as :file option.
|
7
|
+
# file path should be relative to controller's dirname.
|
8
|
+
# also a block accepted for :file option. the block will be executed at controllers's instance level
|
9
|
+
# and should return path to template file.
|
10
|
+
# file name should NOT include extension.
|
11
|
+
#
|
12
|
+
# if a block given NO file will be searched and returned value will be used as template.
|
13
|
+
#
|
14
|
+
# @note files will be searched relative to controller's dirname,
|
15
|
+
# that's it, the folder controller was defined in and operates from.
|
16
|
+
#
|
17
|
+
# @note when searching for file multiple extensions will be tried,
|
18
|
+
# that's it, all extensions controller's engine actually supports.
|
19
|
+
#
|
20
|
+
# @note controllers that inherits named templates will always search for files in own dirname.
|
21
|
+
# controllers that inherits :file templates will search files in the original controller's dirname.
|
22
|
+
#
|
23
|
+
# @example define :items template.
|
24
|
+
# ./items.erb file will be used
|
25
|
+
# define_template :items
|
26
|
+
#
|
27
|
+
# @example define :items template.
|
28
|
+
# ../shared_templates/items.erb file will be used
|
29
|
+
# define_template :items, file: '../shared_templates/items'
|
30
|
+
#
|
31
|
+
# @example define :items template.
|
32
|
+
# ./admin-items.erb file to be used when user logged in and ./items.erb otherwise
|
33
|
+
#
|
34
|
+
# define_template :items, file: -> {user? ? 'admin-items' : 'items'}
|
35
|
+
#
|
36
|
+
# @example define :items template using a block that returns the template string.
|
37
|
+
# no file will be used.
|
38
|
+
#
|
39
|
+
# define_template(:items) do
|
40
|
+
# template = Templates.find_by(id: params[:template_id]) || halt(400, 'Template not found')
|
41
|
+
# template.source
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# @param name
|
45
|
+
# @param file
|
46
|
+
# @param block
|
47
|
+
#
|
48
|
+
def self.define_template name, file: nil, &block
|
49
|
+
file && block && raise(ArgumentError, 'both file and block given, please use either one')
|
50
|
+
(@__templates__ ||= {})[name.to_sym] = {block: block, root: dirname, file: file, name: name}.freeze
|
51
|
+
define_templates_methods
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.define_templates_methods source = self
|
55
|
+
return unless source.instance_variables.include?(:@__templates__)
|
56
|
+
templates = source.instance_variable_get(:@__templates__).each_with_object(allocate.templates.dup) do |(name,setup),o|
|
57
|
+
o[name] = :"__#{name}_template__"
|
58
|
+
if setup[:block]
|
59
|
+
# block given, do not search for file, use returned value instead
|
60
|
+
api.delete define_method(o[name], &setup[:block])
|
61
|
+
elsif setup[:file]
|
62
|
+
# file given, search the file in original controller dirname
|
63
|
+
meth_name = :"__#{name}_template_file__"
|
64
|
+
meth_proc = setup[:file].is_a?(Proc) ? setup[:file] : -> {setup[:file]}
|
65
|
+
api.delete define_method(meth_name, &meth_proc)
|
66
|
+
api.delete define_method(o[name]) {
|
67
|
+
engine, * = resolve_engine
|
68
|
+
read_template(find_template(setup[:root], __send__(meth_name), engine))
|
69
|
+
}
|
70
|
+
else
|
71
|
+
# only name given, search for a file with same name in controller's dirname
|
72
|
+
api.delete define_method(o[name]) {
|
73
|
+
engine, * = resolve_engine
|
74
|
+
read_template(find_template(self.dirname, setup[:name], engine))
|
75
|
+
}
|
76
|
+
end
|
77
|
+
end.freeze
|
78
|
+
api.delete define_method(:templates) {templates}
|
79
|
+
end
|
80
|
+
|
81
|
+
def templates; RocketIO::EMPTY_HASH end
|
82
|
+
end
|
83
|
+
end
|
data/rocketio.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require File.expand_path('../lib/rocketio-views/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.name = 'rocketio-views'
|
6
|
+
spec.version = String.new(RocketIO::Views::VERSION)
|
7
|
+
spec.authors = ['Slee Woo']
|
8
|
+
spec.email = ['mail@sleewoo.com']
|
9
|
+
spec.summary = [spec.name, spec.version]*'-',
|
10
|
+
spec.description = 'View layer for RocketIO'
|
11
|
+
spec.homepage = 'https://github.com/rocketio/' + spec.name
|
12
|
+
spec.license = 'MIT'
|
13
|
+
|
14
|
+
spec.files = Dir['**/{*,.[a-z]*}'].reject {|e| e =~ /(gem|lock)\z/}
|
15
|
+
spec.require_paths = ['lib']
|
16
|
+
|
17
|
+
spec.required_ruby_version = '~> 2.2'
|
18
|
+
|
19
|
+
spec.add_runtime_dependency 'rocketio', '>= 0.4'
|
20
|
+
spec.add_runtime_dependency 'tilt', '~> 2'
|
21
|
+
|
22
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
23
|
+
spec.add_development_dependency 'rake', '~> 10.5'
|
24
|
+
spec.add_development_dependency 'tokyo', '~> 0'
|
25
|
+
spec.add_development_dependency 'rack-radar', '~> 0'
|
26
|
+
spec.add_development_dependency 'pry', '~> 0'
|
27
|
+
spec.add_development_dependency 'pry-byebug', '~> 3'
|
28
|
+
end
|
data/test/b.erb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
<%= @var %>
|
data/test/c.erb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
<%= var %>
|
data/test/engine_test.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'setup'
|
2
|
+
|
3
|
+
spec :engine do
|
4
|
+
it 'inherits engine from superclass' do
|
5
|
+
a = mock_controller {
|
6
|
+
engine :Slim
|
7
|
+
}
|
8
|
+
b = mock_controller(a) {
|
9
|
+
}
|
10
|
+
assert(b.new.engine) == [:SlimTemplate, []]
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'overrides engine inherited from superclass' do
|
14
|
+
a = mock_controller {
|
15
|
+
engine :Slim
|
16
|
+
}
|
17
|
+
b = mock_controller(a) {
|
18
|
+
engine :Turbo
|
19
|
+
}
|
20
|
+
assert(b.new.engine) == [:TurboTemplate, []]
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'can use include to override engine inherited from superclass' do
|
24
|
+
a = mock_controller {
|
25
|
+
engine :Slim
|
26
|
+
}
|
27
|
+
b = mock_controller(a) {
|
28
|
+
engine :Turbo
|
29
|
+
}
|
30
|
+
c = mock_controller(a) {
|
31
|
+
import :engine, from: b
|
32
|
+
}
|
33
|
+
assert(c.new.engine) == [:TurboTemplate, []]
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'uses a block (that runs at instance level) to define engine' do
|
37
|
+
app mock_controller {
|
38
|
+
engine {@x = 'x'; :Slim}
|
39
|
+
def index; engine[0].to_s + @x end
|
40
|
+
}
|
41
|
+
get
|
42
|
+
assert(last_response.body) == 'SlimTemplatex'
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'can accept engine options when a block used' do
|
46
|
+
c = mock_controller {
|
47
|
+
engine {[:Slim, {pretty_print: true}]}
|
48
|
+
}
|
49
|
+
e,o = c.allocate.engine
|
50
|
+
assert(e) == :SlimTemplate
|
51
|
+
assert(o) == [{pretty_print: true}]
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'uses inherited engine if given block returns no engine' do
|
55
|
+
a = mock_controller {
|
56
|
+
engine :Slim
|
57
|
+
}
|
58
|
+
b = mock_controller(a) {
|
59
|
+
engine {
|
60
|
+
:ERB if env['IAMABOT']
|
61
|
+
}
|
62
|
+
def index; engine[0].to_s end
|
63
|
+
}
|
64
|
+
app b
|
65
|
+
get
|
66
|
+
assert(last_response.body) == 'SlimTemplate'
|
67
|
+
env['IAMABOT'] = true
|
68
|
+
get
|
69
|
+
assert(last_response.body) == 'ERBTemplate'
|
70
|
+
end
|
71
|
+
end
|
data/test/index.erb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
<%= url %>
|
data/test/items.erb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
items
|
data/test/layout.erb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
=<%= yield %>=
|
data/test/layout_test.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'setup'
|
2
|
+
|
3
|
+
spec :Views do
|
4
|
+
context :layouts do
|
5
|
+
it 'inherits layout from superclass' do
|
6
|
+
a = mock_controller {
|
7
|
+
layout :master
|
8
|
+
}
|
9
|
+
b = mock_controller(a) {
|
10
|
+
}
|
11
|
+
assert(b.new.layout) == :master
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'overrides layout inherited from superclass' do
|
15
|
+
a = mock_controller {
|
16
|
+
layout :master
|
17
|
+
}
|
18
|
+
b = mock_controller(a) {
|
19
|
+
layout :baster
|
20
|
+
}
|
21
|
+
assert(b.new.layout) == :baster
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'can use `inherit` to override layout inherited from superclass' do
|
25
|
+
a = mock_controller {
|
26
|
+
layout :master
|
27
|
+
}
|
28
|
+
b = mock_controller(a) {
|
29
|
+
layout :baster
|
30
|
+
}
|
31
|
+
c = mock_controller(a) {
|
32
|
+
import :layout, from: b
|
33
|
+
}
|
34
|
+
assert(c.new.layout) == :baster
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'renders without layout if no default layout set' do
|
38
|
+
app mock_controller {
|
39
|
+
def index; render; end
|
40
|
+
}
|
41
|
+
get
|
42
|
+
assert(last_response).is_ok_with_body('/')
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'renders without layout if default layout set to false' do
|
46
|
+
app mock_controller {
|
47
|
+
layout false
|
48
|
+
def index; render; end
|
49
|
+
}
|
50
|
+
get
|
51
|
+
assert(last_response).is_ok_with_body('/')
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'renders without layout if layout option set to nil or false' do
|
55
|
+
app mock_controller {
|
56
|
+
layout :layout
|
57
|
+
def var; @var end
|
58
|
+
def index; render(layout: nil); end
|
59
|
+
def b; @var = 'x'; render(layout: false); end
|
60
|
+
def c; @var = 'y'; render; end
|
61
|
+
}
|
62
|
+
get
|
63
|
+
assert(last_response).is_ok_with_body('/')
|
64
|
+
|
65
|
+
get :b
|
66
|
+
assert(last_response).is_ok_with_body('x')
|
67
|
+
|
68
|
+
get :c
|
69
|
+
assert(last_response).is_ok_with_body("=y=\n")
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'renders with a defined layout' do
|
73
|
+
app mock_controller {
|
74
|
+
define_layout(:master) {'=<%= yield %>='}
|
75
|
+
layout :master
|
76
|
+
def index; render; end
|
77
|
+
}
|
78
|
+
get
|
79
|
+
assert(last_response).is_ok_with_body('=/=')
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'prefers defined layouts over files' do
|
83
|
+
app mock_controller {
|
84
|
+
define_layout(:layout) {'-<%= yield %>-'}
|
85
|
+
layout :layout
|
86
|
+
def index; render; end
|
87
|
+
}
|
88
|
+
get
|
89
|
+
assert(last_response).is_ok_with_body('-/-')
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'prefers explicit layouts over implicit ones' do
|
93
|
+
app mock_controller {
|
94
|
+
define_layout(:x) {'x<%= yield %>'}
|
95
|
+
define_layout(:y) {'y<%= yield %>'}
|
96
|
+
layout :x
|
97
|
+
def index; render {''}; end
|
98
|
+
def b; render(layout: :y) {''}; end
|
99
|
+
}
|
100
|
+
|
101
|
+
get
|
102
|
+
assert(last_response).is_ok_with_body('x')
|
103
|
+
|
104
|
+
get :b
|
105
|
+
assert(last_response).is_ok_with_body('y')
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'accepts layout as :template option' do
|
109
|
+
app mock_controller {
|
110
|
+
def index; render_layout(template: '+<%= yield %>+') {'='}; end
|
111
|
+
}
|
112
|
+
get
|
113
|
+
assert(last_response).is_ok_with_body('+=+')
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'raises ArgumentError if both layout name and :template option given' do
|
117
|
+
app mock_controller {
|
118
|
+
def index; render_layout(:x, template: '+<%= yield %>+') {'='}; end
|
119
|
+
}
|
120
|
+
assert {get}.raise ArgumentError, /both/i
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
outside <%= yield %> layout
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'setup'
|
2
|
+
|
3
|
+
spec :layouts do
|
4
|
+
context :compositing do
|
5
|
+
it 'inherits layouts from superclass' do
|
6
|
+
a = mock_controller {
|
7
|
+
define_layout(:a) {'a'}
|
8
|
+
}
|
9
|
+
b = mock_controller(a) {
|
10
|
+
}.new
|
11
|
+
assert(b.__send__ b.layouts[:a]) == 'a'
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'complements layouts inherited from superclass' do
|
15
|
+
a = mock_controller {
|
16
|
+
define_layout(:a) {'a'}
|
17
|
+
}
|
18
|
+
b = mock_controller(a) {
|
19
|
+
define_layout(:b) {'b'}
|
20
|
+
}.new
|
21
|
+
assert(b.__send__ b.layouts[:a]) == 'a'
|
22
|
+
assert(b.__send__ b.layouts[:b]) == 'b'
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'overrides layouts inherited from superclass' do
|
26
|
+
a = mock_controller {
|
27
|
+
define_layout(:x) {'a'}
|
28
|
+
}
|
29
|
+
b = mock_controller(a) {
|
30
|
+
define_layout(:x) {'b'}
|
31
|
+
}.new
|
32
|
+
assert(b.__send__ b.layouts[:x]) == 'b'
|
33
|
+
end
|
34
|
+
|
35
|
+
test '`inherit` overrides layouts inherited from superclass' do
|
36
|
+
a = mock_controller {
|
37
|
+
define_layout(:x) {'a'}
|
38
|
+
}
|
39
|
+
b = mock_controller(a) {
|
40
|
+
define_layout(:x) {'b'}
|
41
|
+
}
|
42
|
+
c = mock_controller(a) {
|
43
|
+
import :layouts, from: b
|
44
|
+
}.new
|
45
|
+
assert(c.__send__ c.layouts[:x]) == 'b'
|
46
|
+
end
|
47
|
+
|
48
|
+
test '`inherit` complements layouts inherited from superclass' do
|
49
|
+
a = mock_controller {
|
50
|
+
define_layout(:a) {'a'}
|
51
|
+
}
|
52
|
+
b = mock_controller(a) {
|
53
|
+
define_layout(:b) {'b'}
|
54
|
+
}
|
55
|
+
c = mock_controller(a) {
|
56
|
+
import :layouts, from: b
|
57
|
+
}.new
|
58
|
+
assert(c.__send__ c.layouts[:a]) == 'a'
|
59
|
+
assert(c.__send__ c.layouts[:b]) == 'b'
|
60
|
+
end
|
61
|
+
|
62
|
+
test '`inherit` overrides defined layouts' do
|
63
|
+
a = mock_controller {
|
64
|
+
define_layout(:x) {'a'}
|
65
|
+
}
|
66
|
+
b = mock_controller {
|
67
|
+
define_layout(:x) {'b'}
|
68
|
+
import :layouts, from: a
|
69
|
+
}.new
|
70
|
+
assert(b.__send__ b.layouts[:x]) == 'a'
|
71
|
+
end
|
72
|
+
|
73
|
+
test '`inherit` complements defined layouts' do
|
74
|
+
a = mock_controller {
|
75
|
+
define_layout(:a) {'a'}
|
76
|
+
}
|
77
|
+
b = mock_controller {
|
78
|
+
define_layout(:b) {'b'}
|
79
|
+
import :layouts, from: a
|
80
|
+
}.new
|
81
|
+
assert(b.__send__ b.layouts[:a]) == 'a'
|
82
|
+
assert(b.__send__ b.layouts[:b]) == 'b'
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'overrides layouts inherited via `inherit`' do
|
86
|
+
a = mock_controller {
|
87
|
+
define_layout(:x) {'a'}
|
88
|
+
}
|
89
|
+
b = mock_controller {
|
90
|
+
import :layouts, from: a
|
91
|
+
define_layout(:x) {'b'}
|
92
|
+
}.new
|
93
|
+
assert(b.__send__ b.layouts[:x]) == 'b'
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context :rendering do
|
98
|
+
it 'uses a file with same name if only name given' do
|
99
|
+
c = mock_controller {
|
100
|
+
define_layout :master
|
101
|
+
}.new
|
102
|
+
assert(c.render_layout(:master) {'yo'}) == "master yo layout\n"
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'raises a TemplateError if there is no file with same name' do
|
106
|
+
layout = rand.to_s
|
107
|
+
c = mock_controller {
|
108
|
+
define_layout layout
|
109
|
+
}.new
|
110
|
+
assert {c.render_layout(layout) {}}.raise RocketIO::TemplateError
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'uses given file - file resides in controller dirname' do
|
114
|
+
c = mock_controller {
|
115
|
+
define_layout :master, file: :layout
|
116
|
+
}.new
|
117
|
+
assert(c.render_layout(:master) {'yo'}) == "=yo=\n"
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'uses given file - file resides outside controller dirname' do
|
121
|
+
c = mock_controller {
|
122
|
+
define_layout :master, file: './layouts/master'
|
123
|
+
}.new
|
124
|
+
assert(c.render_layout(:master) {'yo'}) == "outside yo layout\n"
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'accepts a block for given file' do
|
128
|
+
c = mock_controller {
|
129
|
+
define_layout :master, file: -> {@outside ? './layouts/master' : :master}
|
130
|
+
}.new
|
131
|
+
assert(c.render_layout(:master) {'yo'}) == "master yo layout\n"
|
132
|
+
c.instance_variable_set(:@outside, true)
|
133
|
+
assert(c.render_layout(:master) {'yo'}) == "outside yo layout\n"
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'accepts a block and uses returned value as layout without searching for file' do
|
137
|
+
c = mock_controller {
|
138
|
+
define_layout(:master) {@admin ? ':<%= yield %>:' : '|<%= yield %>|'}
|
139
|
+
}.new
|
140
|
+
assert(c.render_layout(:master) {'yo'}) == "|yo|"
|
141
|
+
c.instance_variable_set(:@admin, true)
|
142
|
+
assert(c.render_layout(:master) {'yo'}) == ":yo:"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|