lotus-view 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -15
  3. data/.travis.yml +6 -0
  4. data/.yardopts +3 -0
  5. data/Gemfile +13 -2
  6. data/README.md +514 -3
  7. data/Rakefile +17 -1
  8. data/lib/lotus/layout.rb +132 -0
  9. data/lib/lotus/presenter.rb +70 -0
  10. data/lib/lotus/view/dsl.rb +247 -0
  11. data/lib/lotus/view/inheritable.rb +50 -0
  12. data/lib/lotus/view/rendering/layout_finder.rb +104 -0
  13. data/lib/lotus/view/rendering/layout_registry.rb +63 -0
  14. data/lib/lotus/view/rendering/layout_scope.rb +138 -0
  15. data/lib/lotus/view/rendering/null_layout.rb +52 -0
  16. data/lib/lotus/view/rendering/null_template.rb +79 -0
  17. data/lib/lotus/view/rendering/partial.rb +29 -0
  18. data/lib/lotus/view/rendering/partial_finder.rb +41 -0
  19. data/lib/lotus/view/rendering/registry.rb +129 -0
  20. data/lib/lotus/view/rendering/scope.rb +48 -0
  21. data/lib/lotus/view/rendering/template.rb +56 -0
  22. data/lib/lotus/view/rendering/template_finder.rb +53 -0
  23. data/lib/lotus/view/rendering/templates_finder.rb +85 -0
  24. data/lib/lotus/view/rendering/view_finder.rb +37 -0
  25. data/lib/lotus/view/rendering.rb +265 -0
  26. data/lib/lotus/view/template.rb +45 -0
  27. data/lib/lotus/view/version.rb +4 -1
  28. data/lib/lotus/view.rb +180 -2
  29. data/lib/lotus-view.rb +1 -0
  30. data/lotus-view.gemspec +15 -11
  31. data/test/fixtures/templates/app/app_view.html.erb +0 -0
  32. data/test/fixtures/templates/app/view.html.erb +0 -0
  33. data/test/fixtures/templates/application.html.erb +10 -0
  34. data/test/fixtures/templates/articles/_form.html.erb +4 -0
  35. data/test/fixtures/templates/articles/alternative_new.html.erb +1 -0
  36. data/test/fixtures/templates/articles/index.atom.erb +5 -0
  37. data/test/fixtures/templates/articles/index.html.erb +3 -0
  38. data/test/fixtures/templates/articles/index.json.erb +9 -0
  39. data/test/fixtures/templates/articles/index.rss.erb +0 -0
  40. data/test/fixtures/templates/articles/new.html.erb +7 -0
  41. data/test/fixtures/templates/articles/show.html.erb +1 -0
  42. data/test/fixtures/templates/articles/show.json.erb +5 -0
  43. data/test/fixtures/templates/contacts/show.html.haml +1 -0
  44. data/test/fixtures/templates/dashboard/index.html.erb +2 -0
  45. data/test/fixtures/templates/hello_world_view.html.erb +1 -0
  46. data/test/fixtures/templates/index_view.html.erb +1 -0
  47. data/test/fixtures/templates/json_render_view.json.erb +3 -0
  48. data/test/fixtures/templates/render_view.html.erb +1 -0
  49. data/test/fixtures/templates/shared/_sidebar.html.erb +1 -0
  50. data/test/fixtures.rb +187 -0
  51. data/test/layout_test.rb +10 -0
  52. data/test/load_test.rb +79 -0
  53. data/test/presenter_test.rb +31 -0
  54. data/test/rendering_test.rb +125 -0
  55. data/test/root_test.rb +38 -0
  56. data/test/test_helper.rb +24 -0
  57. data/test/version_test.rb +7 -0
  58. data/test/view_test.rb +27 -0
  59. metadata +137 -10
@@ -0,0 +1,138 @@
1
+ module Lotus
2
+ module View
3
+ module Rendering
4
+ # Scope for layout rendering
5
+ #
6
+ # @since 0.1.0
7
+ class LayoutScope
8
+ # Initialize the scope
9
+ #
10
+ # @param layout [Lotus::Layout] the layout to render
11
+ # @param scope [Lotus::View::Rendering::Scope] the scope of the current
12
+ # view
13
+ #
14
+ # @api private
15
+ # @since 0.1.0
16
+ def initialize(layout, scope)
17
+ @layout, @scope = layout, scope
18
+ end
19
+
20
+ # Render a partial or a template within a layout template.
21
+ #
22
+ # @param options [Hash]
23
+ # @option options [String] :partial the partial template to render
24
+ # @option options [String] :template the template to render
25
+ #
26
+ # @return [String] the output of the rendering process
27
+ #
28
+ # @since 0.1.0
29
+ #
30
+ # @example Rendering partial
31
+ # # Given a partial under:
32
+ # # templates/shared/_sidebar.html.erb
33
+ # #
34
+ # # In the layout template:
35
+ # # templates/application.html.erb
36
+ # #
37
+ # # Use like this:
38
+ # <%= render partial: 'shared/sidebar' %>
39
+ #
40
+ # @example Rendering template
41
+ # # Given a template under:
42
+ # # templates/articles/index.html.erb
43
+ # #
44
+ # # In the layout template:
45
+ # # templates/application.html.erb
46
+ # #
47
+ # # Use like this:
48
+ # <%= render template: 'articles/index' %>
49
+ #
50
+ # @example Rendering partial, using optional :locals
51
+ # # Given a partial under:
52
+ # # templates/shared/_sidebar.html.erb
53
+ # #
54
+ # # In the layout template:
55
+ # # templates/application.html.erb
56
+ # #
57
+ # # Use like this:
58
+ # <%= render partial: 'shared/sidebar', { user: current_user } %>
59
+ #
60
+ # #
61
+ # # `user` will be available in the scope of the sidebar rendering
62
+ def render(options)
63
+ renderer(options).render
64
+ end
65
+
66
+ # Returns the requested format.
67
+ #
68
+ # @return [Symbol] the requested format (eg. :html, :json, :xml, etc..)
69
+ #
70
+ # @since 0.1.0
71
+ def format
72
+ @scope.format
73
+ end
74
+
75
+ # The current view.
76
+ #
77
+ # @return [Lotus::View] the current view
78
+ #
79
+ # @since 0.1.0
80
+ def view
81
+ @view || @scope.view
82
+ end
83
+
84
+ # The current locals.
85
+ #
86
+ # @return [Hash] the current locals
87
+ #
88
+ # @since 0.1.0
89
+ def locals
90
+ @locals || @scope.locals
91
+ end
92
+
93
+ protected
94
+ # Forward all the missing methods to the view scope or to the layout.
95
+ #
96
+ # @api private
97
+ # @since 0.1.0
98
+ #
99
+ # @see Lotus::View::Rendering::Scope
100
+ # @see Lotus::Layout
101
+ #
102
+ # @example
103
+ # # In the layout template:
104
+ # # templates/application.html.erb
105
+ # #
106
+ # # Use like this:
107
+ # <title><%= article.title %></title>
108
+ #
109
+ # # `article` will be looked up in the view scope first.
110
+ # # If not found, it will be searched within the layout.
111
+ def method_missing(m)
112
+ begin
113
+ @scope.__send__ m
114
+ rescue
115
+ @layout.__send__ m
116
+ end
117
+ end
118
+
119
+ def renderer(options)
120
+ if options[:partial]
121
+ Rendering::Partial
122
+ elsif options[:template]
123
+ Rendering::Template
124
+ end.new(view, _options(options))
125
+ end
126
+
127
+ private
128
+ def _options(options)
129
+ options.dup.tap do |opts|
130
+ opts.merge!(format: format)
131
+ opts[:locals] ||= {}
132
+ opts[:locals].merge!(locals)
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,52 @@
1
+ module Lotus
2
+ module View
3
+ module Rendering
4
+ # Null Object pattern for Layout.
5
+ # It's used when a view doesn't require a layout.
6
+ #
7
+ # @api private
8
+ # @since 0.1.0
9
+ #
10
+ # @example
11
+ # require 'lotus/view'
12
+ #
13
+ # module Articles
14
+ # class Show
15
+ # include Lotus::View
16
+ # layout nil
17
+ # end
18
+ # end
19
+ #
20
+ # # In this scenario we will use a `NullLayout`.
21
+ class NullLayout
22
+
23
+ # Initialize a layout
24
+ #
25
+ # @param scope [Lotus::View::Rendering::Scope] view rendering scope
26
+ # @param rendered [String] the output of the view rendering process
27
+ #
28
+ # @api private
29
+ # @since 0.1.0
30
+ #
31
+ # @see Lotus::Layout#initialize
32
+ # @see Lotus::View::Rendering#render
33
+ def initialize(scope, rendered)
34
+ @rendered = rendered
35
+ end
36
+
37
+ # Render the layout
38
+ #
39
+ # @return [String] the output of the rendering process
40
+ #
41
+ # @api private
42
+ # @since 0.1.0
43
+ #
44
+ # @see Lotus::Layout#render
45
+ # @see Lotus::View::Rendering#render
46
+ def render
47
+ @rendered
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,79 @@
1
+ module Lotus
2
+ module View
3
+ module Rendering
4
+ # Null Object pattern for layout template
5
+ #
6
+ # It's used when a layout doesn't have an associated template.
7
+ #
8
+ # A common scenario is for non-html requests.
9
+ # Usually we have a template for the application layout
10
+ # (eg `templates/application.html.erb`), but we don't use to have the a
11
+ # template for JSON requests (eg `templates/application.json.erb`).
12
+ # Because most of the times, we only return the output of the view.
13
+ #
14
+ # @api private
15
+ # @since 0.1.0
16
+ #
17
+ # @example
18
+ # require 'lotus/view'
19
+ #
20
+ # # We have an ApplicationLayout (views/application_layout.rb):
21
+ # class ApplicationLayout
22
+ # include Lotus::Layout
23
+ # end
24
+ #
25
+ # # Our layout has a template for HTML requests, located at:
26
+ # # templates/application.html.erb
27
+ #
28
+ # # We set it as global layout
29
+ # Lotus::View.layout = :application
30
+ #
31
+ # # We have two views for HTML and JSON articles.
32
+ # # They have a template each:
33
+ # #
34
+ # # * templates/articles/show.html.erb
35
+ # # * templates/articles/show.json.erb
36
+ # module Articles
37
+ # class Show
38
+ # include Lotus::View
39
+ # format :html
40
+ # end
41
+ #
42
+ # class JsonShow < Show
43
+ # format :json
44
+ # end
45
+ # end
46
+ #
47
+ # # We initialize the framework
48
+ # Lotus::View.load!
49
+ #
50
+ #
51
+ #
52
+ # # When we ask for a HTML rendering, it will use `Articles::Show` and
53
+ # # ApplicationLayout. The output will be a composition of:
54
+ # #
55
+ # # * templates/articles/show.html.erb
56
+ # # * templates/application.html.erb
57
+ #
58
+ # # When we ask for a JSON rendering, it will use `Articles::JsonShow`
59
+ # # and ApplicationLayout. Since, the layout doesn't have any associated
60
+ # # template for JSON, the output will be a composition of:
61
+ # #
62
+ # # * templates/articles/show.json.erb
63
+ class NullTemplate
64
+ # Render the layout template
65
+ #
66
+ # @return [String] the output of the rendering process
67
+ #
68
+ # @api private
69
+ # @since 0.1.0
70
+ #
71
+ # @see Lotus::Layout#render
72
+ # @see Lotus::View::Rendering#render
73
+ def render(scope, locals = {}, &blk)
74
+ blk.call
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,29 @@
1
+ require 'lotus/view/rendering/partial_finder'
2
+
3
+ module Lotus
4
+ module View
5
+ module Rendering
6
+ # Rendering partial
7
+ #
8
+ # It's used when a template wants to render a partial.
9
+ #
10
+ # @api private
11
+ # @since 0.1.0
12
+ #
13
+ # @see Lotus::View::Rendering::Template
14
+ # @see Lotus::View::Rendering::LayoutScope#render
15
+ #
16
+ # @example
17
+ # # We have an application template (templates/application.html.erb)
18
+ # # that uses the following line:
19
+ #
20
+ # <%= render partial: 'shared/sidebar' %>
21
+ class Partial < Template
22
+ protected
23
+ def template
24
+ PartialFinder.new(@view.class, @options).find
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,41 @@
1
+ require 'lotus/view/rendering/template_finder'
2
+
3
+ module Lotus
4
+ module View
5
+ module Rendering
6
+ # Find a partial for the current view context.
7
+ # It's used when a template wants to render a partial.
8
+ #
9
+ # @see Lotus::View::Rendering::Partial
10
+ # @see Lotus::View::Rendering::TemplateFinder
11
+ #
12
+ # @api private
13
+ # @since 0.1.0
14
+ class PartialFinder < TemplateFinder
15
+ # Template file name prefix.
16
+ # By convention a partial file name starts with this prefix.
17
+ #
18
+ # @api private
19
+ # @since 0.1.0
20
+ #
21
+ # @example
22
+ # "_sidebar.html.erb"
23
+ PREFIX = '_'.freeze
24
+
25
+ protected
26
+ def template_name
27
+ *all, last = partial_name.split(separator)
28
+ all.push( last.prepend(prefix) ).join(separator)
29
+ end
30
+
31
+ def partial_name
32
+ @options[:partial]
33
+ end
34
+
35
+ def prefix
36
+ PREFIX
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,129 @@
1
+ require 'lotus/view/rendering/layout_registry'
2
+ require 'lotus/view/rendering/view_finder'
3
+
4
+ module Lotus
5
+ module View
6
+ module Rendering
7
+ # Holds all the references of all the registered subclasses of a view.
8
+ # We have one registry for each superclass view.
9
+ #
10
+ # @api private
11
+ # @since 0.1.0
12
+ #
13
+ # @see Lotus::View::Rendering::LayoutRegistry
14
+ # @see Lotus::View::Rendering#registry
15
+ #
16
+ # @example
17
+ # require 'lotus/view'
18
+ #
19
+ # module Articles
20
+ # class Index
21
+ # include Lotus::View
22
+ # end
23
+ #
24
+ # class Show
25
+ # include Lotus::View
26
+ # end
27
+ #
28
+ # class JsonShow < Show
29
+ # format :json
30
+ # end
31
+ #
32
+ # class XmlShow < Show
33
+ # format :xml
34
+ #
35
+ # def render
36
+ # ArticleSerializer.new(article).to_xml
37
+ # end
38
+ # end
39
+ # end
40
+ #
41
+ # # We have the following templates:
42
+ # #
43
+ # # * articles/index.html.erb
44
+ # # * articles/index.atom.erb
45
+ # # * articles/show.html.erb
46
+ # # * articles/show.json.erb
47
+ #
48
+ # # One registry per superclass view
49
+ # Articles::Index.send(:registry).object_id # => 70135342862240
50
+ #
51
+ # Articles::Show.send(:registry).object_id # => 70135342110540
52
+ # Articles::XmlShow.send(:registry).object_id # => 70135342110540
53
+ # Articles::JsonShow.send(:registry).object_id # => 70135342110540
54
+ #
55
+ #
56
+ #
57
+ # # It holds the references for all the templates and the views
58
+ # Articles::Index.send(:registry).inspect
59
+ # # => { :all => [Articles::Index, nil],
60
+ # # :atom => [Articles::Index, #<Lotus::View::Template ... @file="/path/to/templates/articles/index.atom.erb"],
61
+ # # :html => [Articles::Index, #<Lotus::View::Template ... @file="/path/to/templates/articles/index.html.erb"] }
62
+ #
63
+ # Articles::Show.send(:registry).inspect
64
+ # # => { :all => [Articles::Show, nil],
65
+ # # :html => [Articles::Show, #<Lotus::View::Template ... @file="/path/to/templates/articles/show.html.erb"],
66
+ # # :json => [Articles::JsonShow, #<Lotus::View::Template ... @file="/path/to/templates/articles/show.json.erb"],
67
+ # # :xml => [Articles::XmlShow, nil] }
68
+ class Registry < LayoutRegistry
69
+ # Default format for views without an explicit format.
70
+ #
71
+ # @api private
72
+ # @since 0.1.0
73
+ #
74
+ # @see Lotus::View::Dsl#format
75
+ DEFAULT_FORMAT = :all
76
+
77
+ # Returns the view for the given context.
78
+ #
79
+ # @param context [Hash] the rendering context
80
+ # @option context [Symbol] :format the requested format
81
+ # @param locals [Hash] the set of available objects
82
+ #
83
+ # @return [Lotus::View] the view associated with the given context
84
+ #
85
+ # @raise [Lotus::View::MissingFormatError] if the given context doesn't
86
+ # have the :format key
87
+ #
88
+ # @api private
89
+ # @since 0.1.0
90
+ #
91
+ # @see Lotus::View::Rendering#render
92
+ def resolve(context)
93
+ view, template = fetch(format(context)) { self[DEFAULT_FORMAT] }
94
+ view.new(template, context)
95
+ end
96
+
97
+ private
98
+ def prepare!
99
+ prepare_views!
100
+ prepare_templates!
101
+ end
102
+
103
+ def prepare_views!
104
+ views.each do |view|
105
+ merge! view.format || DEFAULT_FORMAT => [ view, template_for(view) ]
106
+ end
107
+ end
108
+
109
+ def prepare_templates!
110
+ templates.each do |template|
111
+ merge! template.format => [ view_for(template), template ]
112
+ end
113
+ end
114
+
115
+ def views
116
+ @view.subclasses + [ @view ]
117
+ end
118
+
119
+ def view_for(template)
120
+ ViewFinder.new(@view).find(template)
121
+ end
122
+
123
+ def template_for(view)
124
+ templates.find {|template| template.format == view.format }
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,48 @@
1
+ require 'lotus/view/rendering/layout_scope'
2
+ require 'lotus/view/rendering/template'
3
+ require 'lotus/view/rendering/partial'
4
+
5
+ module Lotus
6
+ module View
7
+ module Rendering
8
+ # Rendering scope
9
+ #
10
+ # @since 0.1.0
11
+ #
12
+ # @see Lotus::View::Rendering::LayoutScope
13
+ class Scope < LayoutScope
14
+ # Initialize the scope
15
+ #
16
+ # @param view [Class] the view
17
+ # @param locals [Hash] a set of objects available during the rendering
18
+ # @option locals [Symbol] :format the requested format
19
+ #
20
+ # @api private
21
+ # @since 0.1.0
22
+ def initialize(view, locals = {})
23
+ @view, @locals = view, locals
24
+ end
25
+
26
+ # Returns the requested format.
27
+ #
28
+ # @return [Symbol] the requested format (eg. :html, :json, :xml, etc..)
29
+ #
30
+ # @since 0.1.0
31
+ def format
32
+ locals[:format]
33
+ end
34
+
35
+ protected
36
+ def method_missing(m)
37
+ if @view.respond_to?(m)
38
+ @view.__send__ m
39
+ elsif @locals.key?(m)
40
+ @locals[m]
41
+ else
42
+ super
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,56 @@
1
+ require 'lotus/view/rendering/template_finder'
2
+
3
+ module Lotus
4
+ module View
5
+ module Rendering
6
+ # Rendering template
7
+ #
8
+ # It's used when a template wants to render another template.
9
+ #
10
+ # @api private
11
+ # @since 0.1.0
12
+ #
13
+ # @see Lotus::View::Rendering::LayoutScope#render
14
+ #
15
+ # @example
16
+ # # We have an application template (templates/application.html.erb)
17
+ # # that uses the following line:
18
+ #
19
+ # <%= render template: 'articles/show' %>
20
+ class Template
21
+ # Initialize a template
22
+ #
23
+ # @param view [Lotus::View] the current view
24
+ # @param options [Hash] the rendering informations
25
+ # @option options [Symbol] :format the current format
26
+ # @option options [Hash] :locals the set of objects available within
27
+ # the rendering context
28
+ #
29
+ # @api private
30
+ # @since 0.1.0
31
+ def initialize(view, options)
32
+ @view, @options = view, options
33
+ end
34
+
35
+ # Render the template.
36
+ #
37
+ # @return [String] the output of the rendering process.
38
+ #
39
+ # @api private
40
+ # @since 0.1.0
41
+ def render
42
+ template.render(scope)
43
+ end
44
+
45
+ protected
46
+ def template
47
+ TemplateFinder.new(@view.class, @options).find
48
+ end
49
+
50
+ def scope
51
+ Scope.new(@view, @options[:locals])
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,53 @@
1
+ require 'lotus/view/rendering/templates_finder'
2
+
3
+ module Lotus
4
+ module View
5
+ module Rendering
6
+ # Find a template for the current view context.
7
+ # It's used when a template wants to render another template.
8
+ #
9
+ # @see Lotus::View::Rendering::Template
10
+ # @see Lotus::View::Rendering::TemplatesFinder
11
+ #
12
+ # @api private
13
+ # @since 0.1.0
14
+ class TemplateFinder < TemplatesFinder
15
+ # Initialize a finder
16
+ #
17
+ # @param view [Class] a view
18
+ # @param options [Hash] the informations about the context
19
+ # @option options [String] :template the template file name
20
+ # @option options [Symbol] :format the requested format
21
+ #
22
+ # @api private
23
+ # @since 0.1.0
24
+ def initialize(view, options)
25
+ super(view)
26
+ @options = options
27
+ end
28
+
29
+ # Find a template for the current view context
30
+ #
31
+ # @return [Lotus::View::Template] the requested template
32
+ #
33
+ # @api private
34
+ # @since 0.1.0
35
+ #
36
+ # @see Lotus::View::Rendering::TemplatesFinder#find
37
+ # @see Lotus::View::Rendering::Template#render
38
+ def find
39
+ super.first
40
+ end
41
+
42
+ protected
43
+ def template_name
44
+ @options[:template]
45
+ end
46
+
47
+ def format
48
+ @options[:format]
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end