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.
@@ -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
@@ -0,0 +1,5 @@
1
+ module RocketIO
2
+ module Views
3
+ VERSION = '0.4.0'.freeze
4
+ end
5
+ end
@@ -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
@@ -0,0 +1 @@
1
+ <%= @var %>
@@ -0,0 +1 @@
1
+ <%= var %>
@@ -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
@@ -0,0 +1 @@
1
+ <%= url %>
@@ -0,0 +1 @@
1
+ items
@@ -0,0 +1 @@
1
+ =<%= yield %>=
@@ -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