view_models 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data/CHANGELOG +26 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.textile +242 -0
  4. data/Rakefile +47 -0
  5. data/VERSION.yml +4 -0
  6. data/generators/view_models/USAGE +6 -0
  7. data/generators/view_models/templates/README +1 -0
  8. data/generators/view_models/templates/spec/view_model_spec.rb +7 -0
  9. data/generators/view_models/templates/view_models/view_model.rb +5 -0
  10. data/generators/view_models/templates/views/_empty.html.haml +0 -0
  11. data/generators/view_models/templates/views/view_models/collection/_collection.html.erb +1 -0
  12. data/generators/view_models/templates/views/view_models/collection/_collection.html.haml +8 -0
  13. data/generators/view_models/templates/views/view_models/collection/_collection.text.erb +6 -0
  14. data/generators/view_models/templates/views/view_models/collection/_list.html.erb +1 -0
  15. data/generators/view_models/templates/views/view_models/collection/_list.html.haml +7 -0
  16. data/generators/view_models/templates/views/view_models/collection/_list.text.erb +6 -0
  17. data/generators/view_models/templates/views/view_models/collection/_pagination.html.haml +12 -0
  18. data/generators/view_models/templates/views/view_models/collection/_pagination.text.erb +3 -0
  19. data/generators/view_models/templates/views/view_models/collection/_table.html.haml +5 -0
  20. data/generators/view_models/templates/views/view_models/collection/_table.text.erb +10 -0
  21. data/generators/view_models/view_models_generator.rb +47 -0
  22. data/lib/extensions/active_record.rb +19 -0
  23. data/lib/extensions/model_reader.rb +115 -0
  24. data/lib/extensions/view.rb +24 -0
  25. data/lib/helpers/collection.rb +124 -0
  26. data/lib/helpers/rails.rb +59 -0
  27. data/lib/helpers/view.rb +22 -0
  28. data/lib/view_models/base.rb +268 -0
  29. data/lib/view_models/controller_extractor.rb +24 -0
  30. data/lib/view_models/path_store.rb +61 -0
  31. data/lib/view_models/render_options.rb +109 -0
  32. data/lib/view_models/view.rb +26 -0
  33. data/lib/view_models.rb +3 -0
  34. data/rails/init.rb +18 -0
  35. data/spec/integration/integration_spec.rb +269 -0
  36. data/spec/integration/models/sub_subclass.rb +14 -0
  37. data/spec/integration/models/subclass.rb +3 -0
  38. data/spec/integration/view_models/project.rb +14 -0
  39. data/spec/integration/view_models/sub_subclass.rb +42 -0
  40. data/spec/integration/view_models/subclass.rb +1 -0
  41. data/spec/integration/views/view_models/collection/_collection.html.erb +1 -0
  42. data/spec/integration/views/view_models/collection/_collection.text.erb +6 -0
  43. data/spec/integration/views/view_models/collection/_list.html.erb +1 -0
  44. data/spec/integration/views/view_models/collection/_list.text.erb +6 -0
  45. data/spec/integration/views/view_models/sub_subclass/_capture_in_template.erb +2 -0
  46. data/spec/integration/views/view_models/sub_subclass/_capture_in_view_model.erb +3 -0
  47. data/spec/integration/views/view_models/sub_subclass/_collection_example.html.erb +3 -0
  48. data/spec/integration/views/view_models/sub_subclass/_collection_example.text.erb +3 -0
  49. data/spec/integration/views/view_models/sub_subclass/_collection_item.html.erb +1 -0
  50. data/spec/integration/views/view_models/sub_subclass/_collection_item.text.erb +1 -0
  51. data/spec/integration/views/view_models/sub_subclass/_exists.erb +1 -0
  52. data/spec/integration/views/view_models/sub_subclass/_exists.html.erb +1 -0
  53. data/spec/integration/views/view_models/sub_subclass/_exists.text.erb +1 -0
  54. data/spec/integration/views/view_models/sub_subclass/_exists_in_both.erb +1 -0
  55. data/spec/integration/views/view_models/sub_subclass/_inner.also_explicit.erb +1 -0
  56. data/spec/integration/views/view_models/sub_subclass/_inner.nesting.erb +1 -0
  57. data/spec/integration/views/view_models/sub_subclass/_list_example.html.erb +3 -0
  58. data/spec/integration/views/view_models/sub_subclass/_list_example.text.erb +3 -0
  59. data/spec/integration/views/view_models/sub_subclass/_list_item.html.erb +1 -0
  60. data/spec/integration/views/view_models/sub_subclass/_list_item.text.erb +1 -0
  61. data/spec/integration/views/view_models/sub_subclass/_outer.explicit.erb +1 -0
  62. data/spec/integration/views/view_models/sub_subclass/_outer.nesting.erb +1 -0
  63. data/spec/integration/views/view_models/sub_subclass/_part_that_is_dependent_on_the_view_model.erb +1 -0
  64. data/spec/integration/views/view_models/sub_subclass/show.html.erb +1 -0
  65. data/spec/integration/views/view_models/sub_subclass/show.text.erb +1 -0
  66. data/spec/integration/views/view_models/subclass/_exists_in_both.erb +1 -0
  67. data/spec/integration/views/view_models/subclass/_no_sub_subclass.erb +1 -0
  68. data/spec/integration/views/view_models/subclass/_not_found_in_sub_subclass.erb +1 -0
  69. data/spec/lib/extensions/active_record_spec.rb +31 -0
  70. data/spec/lib/extensions/model_reader_spec.rb +93 -0
  71. data/spec/lib/helpers/collection_spec.rb +196 -0
  72. data/spec/lib/helpers/rails_spec.rb +88 -0
  73. data/spec/lib/helpers/view_spec.rb +20 -0
  74. data/spec/lib/view_models/base_spec.rb +102 -0
  75. data/spec/lib/view_models/view_spec.rb +9 -0
  76. data/spec/spec_helper.rb +14 -0
  77. data/spec/spec_helper_extensions.rb +13 -0
  78. metadata +156 -0
@@ -0,0 +1,124 @@
1
+ module ViewModels
2
+ module Helpers
3
+ module Rails
4
+
5
+ # Construct a view_model for a collection.
6
+ #
7
+ def collection_view_model_for array_or_pagination, context = self
8
+ Collection.new array_or_pagination, context
9
+ end
10
+
11
+ # The Collection view_model helper has the purpose of presenting presentable collections.
12
+ # * Render as list
13
+ # * Render as table
14
+ # * Render as collection
15
+ # * Render a pagination
16
+ #
17
+ class Collection
18
+
19
+ #
20
+ #
21
+ methods_to_delegate = [Enumerable.instance_methods.map(&:to_sym),
22
+ :length, :size, :empty?, :each, :exit,
23
+ { :to => :@collection }].flatten
24
+ self.delegate *methods_to_delegate
25
+ def select *args, &block # active_support fail?
26
+ @collection.select *args, &block
27
+ end
28
+
29
+ def initialize collection, context
30
+ @collection, @context = collection, context
31
+ end
32
+
33
+ # Renders a list (in the broadest sense of the word).
34
+ #
35
+ # Options:
36
+ # collection => collection to iterate over
37
+ # context => context to render in
38
+ # template_name => template to render for each model element
39
+ # separator => separator between each element
40
+ # By default, uses:
41
+ # * The collection of the collection view_model to iterate over.
42
+ # * The original context given to the collection view_model to render in.
43
+ # * Uses :list_item as the default element template.
44
+ # * Uses a nil separator in html.
45
+ #
46
+ def list options = {}
47
+ default_options = { :collection => @collection, :template_name => :list_item, :separator => nil }
48
+
49
+ render_partial :list, default_options.merge(options)
50
+ end
51
+
52
+ # Renders a collection.
53
+ #
54
+ # Note: The only difference between a list and a collection is the enclosing
55
+ # list type. While list uses ol, the collection uses ul.
56
+ #
57
+ # Options:
58
+ # collection => collection to iterate over
59
+ # context => context to render in
60
+ # template_name => template to render for each model element
61
+ # separator => separator between each element
62
+ # By default, uses:
63
+ # * The collection of the collection view_model to iterate over.
64
+ # * Uses :collection_item as the default element template.
65
+ # * Uses a nil separator.
66
+ #
67
+ def collection options = {}
68
+ default_options = { :collection => @collection, :template_name => :collection_item, :separator => nil }
69
+
70
+ render_partial :collection, default_options.merge(options)
71
+ end
72
+
73
+ # Renders a table.
74
+ #
75
+ # Note: Each item represents a table row.
76
+ #
77
+ # Options:
78
+ # collection => collection to iterate over
79
+ # context => context to render in
80
+ # template_name => template to render for each model element
81
+ # separator => separator between each element
82
+ # By default, uses:
83
+ # * The collection of the collection view_model to iterate over.
84
+ # * Uses :table_row as the default element template.
85
+ # * Uses a nil separator.
86
+ #
87
+ def table options = {}
88
+ default_options = { :collection => @collection, :template_name => :table_row, :separator => nil }
89
+
90
+ render_partial :table, default_options.merge(options)
91
+ end
92
+
93
+ # Renders a pagination.
94
+ #
95
+ # Options:
96
+ # collection => collection to iterate over
97
+ # context => context to render in
98
+ # separator => separator between pages
99
+ # By default, uses:
100
+ # * The collection of the collection view_model to iterate over.
101
+ # * Uses | as separator.
102
+ #
103
+ def pagination options = {}
104
+ default_options = { :collection => @collection, :separator => '|' }
105
+
106
+ render_partial :pagination, default_options.merge(options)
107
+ end
108
+
109
+ private
110
+
111
+ # Helper method that renders a partial in the context of the context instance.
112
+ #
113
+ # Example:
114
+ # If the collection view_model helper has been instantiated in the context
115
+ # of a controller, render will be called in the controller.
116
+ #
117
+ def render_partial name, locals
118
+ @context.instance_eval { render :partial => "view_models/collection/#{name}", :locals => locals }
119
+ end
120
+ end
121
+
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,59 @@
1
+ module ViewModels
2
+ module Helpers
3
+ module Rails
4
+
5
+ mattr_accessor :specific_view_model_mapping
6
+ self.specific_view_model_mapping = {}
7
+
8
+ # Create a new view_model instance for the given model instance
9
+ # with the given arguments.
10
+ #
11
+ # Note: Will emit an ArgumentError if the view model class doesn't support 2 arguments.
12
+ #
13
+ def view_model_for model, context = self
14
+ view_model_class_for(model).new model, context
15
+ end
16
+
17
+ # Get the view_model class for the given model instance.
18
+ #
19
+ # Note: ViewModels are usually of class ViewModels::<ModelClassName>.
20
+ # (As returned by default_view_model_class_for)
21
+ # Override specific_mapping if you'd like to install your own.
22
+ #
23
+ # OR: Override default_view_model_class_for(model) if
24
+ # you'd like to change the default.
25
+ #
26
+ def view_model_class_for model
27
+ specific_view_model_class_for(model) || default_view_model_class_for(model)
28
+ end
29
+
30
+ # Returns the default view_model class for the given model instance.
31
+ #
32
+ # Default class name is:
33
+ # ViewModels::<ModelClassName>
34
+ #
35
+ # Override this method if you'd like to change the _default_
36
+ # model-to-view_model class mapping.
37
+ #
38
+ # Note: Will emit a NameError if a corresponding ViewModels constant cannot be loaded.
39
+ #
40
+ mattr_accessor :default_prefix
41
+ self.default_prefix = 'ViewModels::'
42
+ def default_view_model_class_for model
43
+ (default_prefix + model.class.name).constantize
44
+ end
45
+
46
+ # Returns a specific view_model class for the given model instance.
47
+ #
48
+ # Override this method, if you want to return a specific
49
+ # view model class for the given model.
50
+ #
51
+ # Note: Will emit a NameError if a corresponding ViewModels constant cannot be loaded.
52
+ #
53
+ def specific_view_model_class_for model
54
+ specific_view_model_mapping[model.class]
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,22 @@
1
+ module ViewModels
2
+ module Helpers
3
+ # Module for conveniently including common view_helpers into a view_model
4
+ #
5
+ module View
6
+
7
+ # Include hook.
8
+ #
9
+ def self.included view_model
10
+ view_model.send :include, *all_view_helpers
11
+ end
12
+
13
+ def self.all_view_helpers
14
+ [
15
+ ActionView::Helpers,
16
+ ERB::Util
17
+ ]
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,268 @@
1
+ # Base Module for ViewModels.
2
+ #
3
+ module ViewModels
4
+
5
+ # Gets raised when render_as, render_the, or render_template cannot
6
+ # find the named template, not even in the hierarchy.
7
+ #
8
+ class MissingTemplateError < StandardError; end
9
+
10
+ # Base class from which all view_models inherit.
11
+ #
12
+ class Base
13
+
14
+ # Model and Controller are accessible from outside.
15
+ #
16
+ # TODO but they actually shouldn't be. Try to migrate into protected area.
17
+ #
18
+ attr_reader :model, :controller
19
+
20
+ # Make helper and helper_method available
21
+ #
22
+ include ActionController::Helpers
23
+
24
+ # Create a view_model. To create a view_model, you need to have a model (to present) and a context.
25
+ # The context is usually a view or a controller, but doesn't need to be.
26
+ #
27
+ def initialize model, context
28
+ @model = model
29
+ @controller = ControllerExtractor.new(context).extract
30
+ end
31
+
32
+ class << self
33
+
34
+ # Installs a path store, a specific store for
35
+ # template inheritance, to remember specific
36
+ # [path, name, format] tuples, pointing to a template path,
37
+ # so the view models don't have to traverse the inheritance chain always.
38
+ #
39
+ attr_accessor :path_store
40
+ def inherited subclass
41
+ ViewModels::PathStore.install_in subclass
42
+ super
43
+ end
44
+
45
+ # Installs the model_reader Method for filtered
46
+ # model method delegation.
47
+ #
48
+ include Extensions::ModelReader
49
+
50
+ # Delegates method calls to the controller.
51
+ #
52
+ # Examples:
53
+ # controller_method :current_user
54
+ # controller_method :current_user, :current_profile # multiple methods to be delegated
55
+ #
56
+ # In the view_model:
57
+ # self.current_user
58
+ # will call
59
+ # controller.current_user
60
+ #
61
+ def controller_method *methods
62
+ delegate *methods << { :to => :controller }
63
+ end
64
+
65
+ # Wrapper for add_template_helper in ActionController::Helpers, also
66
+ # includes given helper in the view_model
67
+ #
68
+ # TODO extract into module
69
+ #
70
+ alias old_add_template_helper add_template_helper
71
+ def add_template_helper helper_module
72
+ include helper_module
73
+ old_add_template_helper helper_module
74
+ end
75
+
76
+ # Sets the view format and tries to render the given options.
77
+ #
78
+ # Note: Also caches [path, name, format] => template path.
79
+ #
80
+ def render view, options
81
+ options.format! view
82
+ path_store.cached options do
83
+ options.file = template_path view, options
84
+ view.render_with options
85
+ end
86
+ end
87
+
88
+ protected
89
+
90
+ # Returns the next view model class in the render hierarchy.
91
+ #
92
+ # Note: Just returns the superclass.
93
+ #
94
+ # TODO Think about raising the MissingTemplateError here.
95
+ #
96
+ def next
97
+ superclass
98
+ end
99
+
100
+ # Just raises a fitting template error.
101
+ #
102
+ def raise_template_error_with message
103
+ raise MissingTemplateError.new "No template #{message} found."
104
+ end
105
+
106
+ # Check if the view lookup inheritance chain has ended.
107
+ #
108
+ # Raises a MissingTemplateError if yes.
109
+ #
110
+ def inheritance_chain_ends?
111
+ self == ViewModels::Base
112
+ end
113
+
114
+ # Returns a template path for the view with the given options.
115
+ #
116
+ # If no template is found, traverses up the inheritance chain.
117
+ #
118
+ # Raises a MissingTemplateError if none is found during
119
+ # inheritance chain traversal.
120
+ #
121
+ def template_path view, options
122
+ raise_template_error_with options.error_message if inheritance_chain_ends?
123
+
124
+ template_path_from(view, options) || self.next.template_path(view, options)
125
+ end
126
+
127
+ # Accesses the view to find a suitable template path.
128
+ #
129
+ def template_path_from view, options
130
+ template = view.find_template tentative_template_path(options)
131
+
132
+ template && template.path
133
+ end
134
+
135
+ # Return as render path either a stored path or a newly generated one.
136
+ #
137
+ # If nothing or nil is passed, the store is ignored.
138
+ #
139
+ def tentative_template_path options
140
+ path_store[options.path_key] || generate_template_path_from(options)
141
+ end
142
+
143
+ # Returns the root of this view_models views with the template name appended.
144
+ # e.g. 'view_models/some/specific/path/to/template'
145
+ #
146
+ def generate_template_path_from options
147
+ File.join generate_path_from(options), options.name
148
+ end
149
+
150
+ # If the path is explicitly defined, return it, otherwise
151
+ # generate a view model path from the class name.
152
+ #
153
+ def generate_path_from options
154
+ options.path || view_model_path
155
+ end
156
+
157
+ # Returns the path from the view_model_view_paths to the actual templates.
158
+ # e.g. "view_models/models/book"
159
+ #
160
+ # If the class is named
161
+ # ViewModels::Models::Book
162
+ # this method will yield
163
+ # view_models/models/book
164
+ #
165
+ # Note: Remembers the result since it is dependent on the Class name only.
166
+ #
167
+ def view_model_path
168
+ @view_model_path || @view_model_path = self.name.underscore
169
+ end
170
+
171
+ end # class << self
172
+
173
+ # Delegate controller methods.
174
+ #
175
+ controller_method :logger, :form_authenticity_token, :protect_against_forgery?, :request_forgery_protection_token
176
+
177
+ # Make all the dynamically generated routes (restful routes etc.)
178
+ # available in the view_model
179
+ #
180
+ ActionController::Routing::Routes.install_helpers self
181
+
182
+ # Renders the given partial in the view_model's view root in the format given.
183
+ #
184
+ # Example:
185
+ # app/views/view_models/this/view_model/_partial.haml
186
+ # app/views/view_models/this/view_model/_partial.text.erb
187
+ #
188
+ # The following options are supported:
189
+ # * :format - Calling view_model.render_as('partial') will render the haml
190
+ # partial, calling view_model.render_as('partial', :format => :text) will render
191
+ # the text erb.
192
+ # * All other options are passed on to the render call. I.e. if you want to specify locals you can call
193
+ # view_model.render_as(:partial, :locals => { :name => :value })
194
+ # * If no format is given, it will render the default format, which is (currently) html.
195
+ #
196
+ def render_as name, options = {}
197
+ render RenderOptions::Partial.new(name, options)
198
+ end
199
+ # render_the is used for small parts.
200
+ #
201
+ # Example:
202
+ # # If the view_model is called window, the following
203
+ # # is more legible than window.render_as :menubar
204
+ # * window.render_the :menubar
205
+ #
206
+ alias render_the render_as
207
+
208
+ # Renders the given template in the view_model's view root in the format given.
209
+ #
210
+ # Example:
211
+ # app/views/view_models/this/view_model/template.haml
212
+ # app/views/view_models/this/view_model/template name.text.erb
213
+ #
214
+ # The following options are supported:
215
+ # * :format - Calling view_model.render_template('template') will render the haml
216
+ # template, calling view_model.render_template('template', :format => :text) will render
217
+ # the text erb template.
218
+ # * All other options are passed on to the render call. I.e. if you want to specify locals you can call
219
+ # view_model.render_template(:template, :locals => { :name => :value })
220
+ # * If no format is given, it will render the default format, which is (currently) html.
221
+ #
222
+ def render_template name, options = {}
223
+ render RenderOptions::Template.new(name, options)
224
+ end
225
+
226
+ protected
227
+
228
+ # CaptureHelper needs this.
229
+ #
230
+ attr_accessor :output_buffer
231
+
232
+ # Internal render method that uses the options to get a view instance
233
+ # and then referring to its class for rendering.
234
+ #
235
+ def render options
236
+ options.view_model = self
237
+
238
+ determine_and_set_format options
239
+
240
+ self.class.render view_instance, options
241
+ end
242
+
243
+ # Returns a view instance for render_xxx.
244
+ #
245
+ # TODO Try getting a view instance from the controller.
246
+ #
247
+ def view_instance
248
+ # view = if controller.response.template
249
+ # controller.response.template
250
+ # else
251
+ View.new controller, master_helper_module
252
+ # end
253
+
254
+ # view.extend Extensions::View
255
+ end
256
+
257
+ # Determines what format to use for rendering.
258
+ #
259
+ # Note: Uses the template format of the view model instance
260
+ # if none is explicitly set in the options.
261
+ # This propagates the format to further render_xxx calls.
262
+ #
263
+ def determine_and_set_format options
264
+ options.format = @template_format = options.format || @template_format
265
+ end
266
+
267
+ end
268
+ end
@@ -0,0 +1,24 @@
1
+ # Base Module for ViewModels.
2
+ #
3
+ module ViewModels
4
+
5
+ # Extracts controllers for a living from unsuspecting views.
6
+ #
7
+ class ControllerExtractor
8
+
9
+ attr_reader :context
10
+
11
+ def initialize context
12
+ @context = context
13
+ end
14
+
15
+ # Extracts a controller from the context.
16
+ #
17
+ def extract
18
+ context = self.context
19
+ context.respond_to?(:controller) ? context.controller : context
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,61 @@
1
+ # Base Module for ViewModels.
2
+ #
3
+ module ViewModels
4
+
5
+ # A simple path store. Designed to remove a bit of complexity from the base view model.
6
+ #
7
+ # Use it to install an instance in the metaclass.
8
+ #
9
+ class PathStore
10
+
11
+ attr_reader :view_model_class
12
+
13
+ def initialize view_model_class
14
+ @view_model_class = view_model_class
15
+ @name_path_mapping = {}
16
+ end
17
+
18
+ # Install in the metaclass (as an example).
19
+ #
20
+ def self.install_in klass
21
+ klass.path_store = PathStore.new klass
22
+ end
23
+
24
+ # Cache the result of the rendering.
25
+ #
26
+ def cached options, &block
27
+ prepare options.path_key
28
+ result = block.call
29
+ save options and result if result
30
+ end
31
+
32
+ # Prepare the key for the next storing procedure.
33
+ #
34
+ # Note: If this is nil, the store will not save the path.
35
+ #
36
+ def prepare key
37
+ @key = key
38
+ end
39
+
40
+ # Saves the options for the prepared key.
41
+ #
42
+ def save options
43
+ self[@key] = options.file
44
+ end
45
+
46
+ # Does not save values for nil keys.
47
+ #
48
+ def []= key, path
49
+ return if key.nil?
50
+ @name_path_mapping[key] ||= path
51
+ end
52
+
53
+ # Simple [] delegation.
54
+ #
55
+ def [] key
56
+ @name_path_mapping[key]
57
+ end
58
+
59
+ end
60
+
61
+ end
@@ -0,0 +1,109 @@
1
+ module ViewModels
2
+
3
+ # Container object for render options.
4
+ #
5
+ module RenderOptions
6
+
7
+ # Hold a number of options for rendering.
8
+ #
9
+ class Base
10
+
11
+ attr_accessor :path, :name, :prefix, :file, :view_model, :format
12
+
13
+ def initialize prefix, name, options
14
+ @prefix = prefix
15
+ @options = options
16
+ self.template_name = deoptionize name
17
+ @format = @options.delete :format
18
+ end
19
+
20
+ # Generate a suitable error message for the error options.
21
+ #
22
+ def error_message
23
+ "'#{error_path}#{name}' with #{error_format}"
24
+ end
25
+ def error_path
26
+ path = self.path
27
+ path ? "#{path}/" : ""
28
+ end
29
+ def error_format
30
+ format = self.format
31
+ format ? "format #{format}" : "default format"
32
+ end
33
+
34
+ # Used when rendering.
35
+ #
36
+ def to_render_options
37
+ @options[:locals] ||= {}
38
+ @options[:locals].reverse_merge! :view_model => view_model
39
+ @options.reverse_merge :file => file
40
+ end
41
+
42
+ def format! view
43
+ view.template_format = @format if @format
44
+ end
45
+
46
+ # Used for caching.
47
+ #
48
+ def path_key
49
+ [self.path, self.name, self.format]
50
+ end
51
+
52
+ private
53
+
54
+ # TODO rewrite
55
+ #
56
+ def template_name= template_name
57
+ template_name.to_s.include?('/') ? specific_path(template_name) : incomplete_path(template_name)
58
+ end
59
+
60
+ #
61
+ #
62
+ def deoptionize template_name
63
+ if template_name.kind_of?(Hash)
64
+ @options.merge! template_name
65
+ @options.delete :partial
66
+ else
67
+ template_name
68
+ end
69
+ end
70
+
71
+ #
72
+ #
73
+ def specific_path name
74
+ self.path = File.dirname name
75
+ self.name_with_prefix = File.basename name
76
+ end
77
+
78
+ #
79
+ #
80
+ def incomplete_path name
81
+ self.path = nil
82
+ self.name_with_prefix = name
83
+ end
84
+
85
+ def name_with_prefix= name
86
+ self.name = "#{self.prefix}#{name}"
87
+ end
88
+
89
+ end
90
+
91
+ # A specific container for partial rendering.
92
+ #
93
+ class Partial < Base
94
+ def initialize name, options = {}
95
+ super :'_', name, options
96
+ end
97
+ end
98
+
99
+ # A specific container for template rendering.
100
+ #
101
+ class Template < Base
102
+ def initialize name, options = {}
103
+ super nil, name, options
104
+ end
105
+ end
106
+
107
+ end
108
+
109
+ end
@@ -0,0 +1,26 @@
1
+ module ViewModels
2
+ # View model specific view.
3
+ #
4
+ class View < ActionView::Base
5
+
6
+ # Include the helpers from the view model.
7
+ #
8
+ def initialize controller, master_helper_module
9
+ metaclass.send :include, master_helper_module
10
+ super controller.class.view_paths, {}, controller
11
+ end
12
+
13
+ #
14
+ #
15
+ def render_with options
16
+ render options.to_render_options
17
+ end
18
+
19
+ # Finds the template in the view paths at the given path, with its format.
20
+ #
21
+ def find_template path
22
+ view_paths.find_template path, template_format rescue nil
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ require 'active_support'
2
+
3
+ module ViewModels; end