view_models 1.5.2

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 (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