lotus-view 0.0.0 → 0.1.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.
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