view_models 1.5.6 → 1.5.7
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +20 -234
- data/TODO.textile +3 -2
- data/VERSION.yml +1 -1
- data/lib/experimental/modules_in_render_hierarchy.rb +3 -3
- data/lib/view_models/render_options.rb +22 -14
- data/spec/integration/integration_spec.rb +1 -1
- data/spec/integration/view_models/module_for_rendering.rb +2 -2
- data/spec/lib/helpers/rails_spec.rb +0 -4
- metadata +3 -3
data/README.textile
CHANGED
@@ -1,22 +1,16 @@
|
|
1
|
-
h1. ViewModels
|
1
|
+
h1. ViewModels
|
2
2
|
|
3
|
-
|
3
|
+
A view model/representer solution for Rails.
|
4
4
|
|
5
|
-
|
5
|
+
h2. Features
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
"Severin":http://github.com/severin @http://github.com/severin@, for bits and pieces here and there.
|
16
|
-
|
17
|
-
"rainhead":http://github.com/rainhead @http://github.com/rainhead@, for the idea to subclass ActionView::Base.
|
18
|
-
|
19
|
-
Important note: These guys rock! :)
|
7
|
+
* Polymorph view model objects that correspond to model objects.
|
8
|
+
* View model specific templates.
|
9
|
+
* Hierarchical Template Rendering: Allows generalized templates for a class tree of view models.
|
10
|
+
* Helper methods directly on the view models.
|
11
|
+
* No view related code in the models.
|
12
|
+
* A clean API for use in Rails.
|
13
|
+
* 100% rcov coverage, nice metrics, gallons of blood and sweat by excellent contributors.
|
20
14
|
|
21
15
|
h2. Installation
|
22
16
|
|
@@ -24,7 +18,7 @@ h3. Rails Plugin
|
|
24
18
|
|
25
19
|
@script/plugin install git://github.com/floere/view_models.git@
|
26
20
|
|
27
|
-
h3. Gem for use in Rails
|
21
|
+
h3. Gem (for use in Rails)
|
28
22
|
|
29
23
|
@gem install view_models@
|
30
24
|
|
@@ -34,221 +28,13 @@ and then adding the line
|
|
34
28
|
|
35
29
|
in your environment.rb.
|
36
30
|
|
37
|
-
h2.
|
38
|
-
|
39
|
-
A possible view_model solution, i.e. no view logic in model code.
|
40
|
-
|
41
|
-
h2. Feedback
|
42
|
-
|
43
|
-
Ask/Write florian.hanke@gmail.com if you have questions/feedback, thanks! :)
|
44
|
-
Fork if you have improvements. Send me a pull request, it is much appreciated.
|
45
|
-
|
46
|
-
h2. Problem
|
47
|
-
|
48
|
-
Display Methods are not well placed either in
|
49
|
-
* models: Violation of the MVC principle.
|
50
|
-
* helpers: No Polymorphism.
|
51
|
-
|
52
|
-
h2. Solution
|
53
|
-
|
54
|
-
A thin proxy layer over a model, with access to the controller, used by the view or controller.
|
55
|
-
|
56
|
-
@The view@ -> to @the view model@ which in turn -> @the model@ and -> @the controller@
|
57
|
-
|
58
|
-
h2. Examples & What you can do
|
59
|
-
|
60
|
-
h3. A quick one
|
61
|
-
|
62
|
-
In the view:
|
63
|
-
<pre><code>user = view_model_for @user
|
64
|
-
%h1= "You, #{user.full_name}, the user"
|
65
|
-
%h2 This is how you look as a search result:
|
66
|
-
= user.render_as :result
|
67
|
-
%h2 This is how you look as a Vcard:
|
68
|
-
= user.render_as :vcard</code></pre>
|
69
|
-
In the view model:
|
70
|
-
<pre><code>class ViewModels::User < ViewModels::Project
|
71
|
-
model_reader :first_name, :last_name
|
72
|
-
def full_name
|
73
|
-
"#{last_name}, #{first_name}"
|
74
|
-
end
|
75
|
-
end</code></pre>
|
76
|
-
In the model:
|
77
|
-
<pre><code>class User < ActiveRecord::Base
|
78
|
-
end</code></pre>
|
79
|
-
Also, there are two partials in @app/views/view_models/user/@, @_result.html.haml@ and @_vcard.html.haml@ that define how the result of @user.render_as :result@ and @user.render_as :vcard@ look. The ViewModel can be accessed inside the view by using the local variable @view_model@.
|
80
|
-
|
81
|
-
h3. Getting a view_model in a view or a controller.
|
82
|
-
|
83
|
-
Call view_model_for: @view_model_instance = view_model_for model_instance@
|
84
|
-
By convention, uses @ViewModels::Model::Class::Name@, thus prefixing @ViewModels::@ to the model class name.
|
85
|
-
|
86
|
-
Note: You can override @specific_view_model_class_for@ to change the mapping of model to class, or make it dynamic.
|
87
|
-
|
88
|
-
h3. Getting a collection view model in a view.
|
89
|
-
|
90
|
-
The collection view_model renders each of the given items with its view_model.
|
91
|
-
|
92
|
-
Call collection_view_model_for: @collection_view_model_instance = collection_view_model_for enumerable_containing_model_instances@
|
93
|
-
|
94
|
-
Rendering a list: @collection_view_model_instance.list@
|
95
|
-
|
96
|
-
Rendering a collection: @collection_view_model_instance.collection@
|
97
|
-
|
98
|
-
Rendering a table: @collection_view_model_instance.table@
|
99
|
-
|
100
|
-
Rendering a pagination: @collection_view_model_instance.pagination@
|
101
|
-
Note: Only works if the passed parameter for @collection_view_model_for@ is a @PaginationEnumeration@.
|
102
|
-
|
103
|
-
Important note:
|
104
|
-
As of yet it is needed to copy the templates/views/view_models/collection
|
105
|
-
directory to the corresponding location in app/views/view_models/collection.
|
106
|
-
This is only needed if you wish to use the collection view model.
|
107
|
-
The collections are automatically copied if you use the generator.
|
108
|
-
|
109
|
-
Note: Rewrite the collection templates as needed, they are rather basic.
|
110
|
-
|
111
|
-
h3. Writing filtered delegate methods on the view model.
|
112
|
-
|
113
|
-
Will create two delegate methods first_name and last_name that delegate to the model: @model_reader :first_name, :last_name@
|
114
|
-
|
115
|
-
Will create a description delegate method that filters the model value through h: @model_reader :description, :filter_through => :h@
|
116
|
-
|
117
|
-
Will create a description delegate method that filters the model value through first textilize, then h: @model_reader :description, :filter_through => [:textilize, :h]@
|
118
|
-
|
119
|
-
Will create both a first_name and last_name delegate method that filters the model value through first textilize, then h: @model_reader :first_name, :last_name, :filter_through => [:textilize, :h]@
|
120
|
-
Note: Filter methods can be any method on the view_model with arity 1.
|
121
|
-
|
122
|
-
h3. Rendering view model templates
|
123
|
-
|
124
|
-
Use @render_as(template_name, options)@.
|
125
|
-
|
126
|
-
Gets a @ViewModels::Model::Class@ instance: @view_model = view_model_for Model::Class.new@
|
127
|
-
|
128
|
-
Gets a @ViewModels::<model_instance.class.name>@ instance: @view_model = view_model_for model_instance@
|
129
|
-
|
130
|
-
Renders the 'example' partial in view_models/model/class: @view_model.render_as :example@
|
131
|
-
Note: Renders a format depending on the request. ../index.text will render example.text.erb.
|
132
|
-
|
133
|
-
Renders the 'example.text.erb' partial in view_models/model/class: @view_model.render_as :example, :format => :text@
|
134
|
-
Note: If the partial cannot be found, it will traverse the view model hierarchy upwards to find a partial template.
|
135
|
-
|
136
|
-
Locals can be passed through as usual: @view_model.render_as :example, :format => :text, :locals => { :name => value }@
|
137
|
-
|
138
|
-
h3. Rails Helpers in ViewModels
|
139
|
-
|
140
|
-
Use @helper@ as you would in the controller.
|
141
|
-
@helper ActionView::Helpers::UrlHelper@
|
142
|
-
@helper ApplicationHelper@
|
143
|
-
Note: It is helpful to create a superclass to all view models in the project with generally used helpers.
|
144
|
-
We use @ViewModels::Project@ a lot, for example. See example below.
|
145
|
-
|
146
|
-
h3. Controller Delegate Methods
|
147
|
-
|
148
|
-
Use @controller_method(*args)@.
|
149
|
-
|
150
|
-
Delegates current_user and logger on the view_model to the controller: @controller_method :current_user, :logger@
|
151
|
-
|
152
|
-
h2. The Generator
|
153
|
-
|
154
|
-
Generates view model class, spec, and views. Use as follows:
|
155
|
-
|
156
|
-
@script/generate view_models <view model class name>@
|
157
|
-
@script/generate view_models User@
|
158
|
-
|
159
|
-
@script/generate view_models <view model class name> <partials for render_as, separated by space>@
|
160
|
-
@script/generate view_models User compact extensive list_item table_item@
|
161
|
-
|
162
|
-
h2. One Big Fat Example
|
163
|
-
|
164
|
-
The following classes all have specs of course ;) But are not shown since they don't help the example.
|
165
|
-
|
166
|
-
@ViewModels@ superclass for this project.
|
167
|
-
|
168
|
-
We include all of Rails' helpers for the view models in this project.
|
169
|
-
Also, we include the @ApplicationHelper@.
|
170
|
-
|
171
|
-
We delegate @logger@ and @current_user@ calls in the view models to the active controller.
|
172
|
-
|
173
|
-
<pre>
|
174
|
-
<code>
|
175
|
-
class ViewModels::Project < ViewModels::Base
|
176
|
-
|
177
|
-
# Our ApplicationHelper.
|
178
|
-
#
|
179
|
-
helper ApplicationHelper
|
180
|
-
|
181
|
-
# We want to be able to call view_model_for in our view_models.
|
182
|
-
#
|
183
|
-
helper ViewModels::Helpers::Rails
|
184
|
-
|
185
|
-
# Include all common view helpers.
|
186
|
-
#
|
187
|
-
helper ViewModels::Helpers::View
|
188
|
-
|
189
|
-
# We want to be able to use id, dom_id, to_param on the view model.
|
190
|
-
#
|
191
|
-
# Note: Overrides the standard dom_id method from the RecordIdentificationHelper.
|
192
|
-
#
|
193
|
-
include ViewModels::Extensions::ActiveRecord
|
194
|
-
|
195
|
-
controller_method :logger, :current_user
|
196
|
-
|
197
|
-
end
|
198
|
-
|
199
|
-
# All items have a description that needs to be filtered by textilize.
|
200
|
-
#
|
201
|
-
class ViewModels::Item < ViewModels::Project
|
202
|
-
model_reader :description, :filter_through => :textilize
|
203
|
-
# Use price in the view as follows:
|
204
|
-
# = view_model.price - will display e.g. 16.57 CHF, since it is filtered first through localize_currency
|
205
|
-
model_reader :price, :filter_through => :localize_currency
|
206
|
-
|
207
|
-
# Converts a database price tag to the users chosen value, with the users preferred currency appended.
|
208
|
-
# If the user is Swiss, localize_currency will convert 10 Euros to "16.57 CHF"
|
209
|
-
#
|
210
|
-
def localize_currency(price_in_euros)
|
211
|
-
converted_price = current_user.convert_price(price_in_euros)
|
212
|
-
"#{converted_price} #{current_user.currency.to_s}"
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
|
-
# This class also has partial templates in the directory
|
217
|
-
# app/views/view_models/book
|
218
|
-
# that are called
|
219
|
-
# _cart_item.html.haml
|
220
|
-
# _cart_item.text.erb
|
221
|
-
#
|
222
|
-
# Call view_model_for on a book in the view or controller to get this view_model.
|
223
|
-
#
|
224
|
-
class ViewModels::Book < ViewModels::Item
|
225
|
-
model_reader :author, :title, :pages
|
226
|
-
model_reader :excerpt, :filter_through => :textilize
|
227
|
-
|
228
|
-
def header
|
229
|
-
content_tag(:h1, "#{author} – #{title}")
|
230
|
-
end
|
231
|
-
|
232
|
-
def full_description
|
233
|
-
content_tag(:p, "#{excerpt} #{description}", :class => 'description full')
|
234
|
-
end
|
235
|
-
end
|
31
|
+
h2. Links Galore!
|
236
32
|
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
class ViewModels::Toy < ViewModels::Item
|
246
|
-
model_reader :starting_age, :small_dangerous_parts
|
247
|
-
|
248
|
-
def obligatory_parental_warning
|
249
|
-
"Warning, this toy can only be used by kids ages #{starting_age} and up. Your department of health. Thank you."
|
250
|
-
end
|
251
|
-
|
252
|
-
end
|
253
|
-
</code>
|
254
|
-
</pre>
|
33
|
+
"Usage, Examples, In-depth Infos [Wiki]":http://wiki.github.com/floere/view_models/
|
34
|
+
"Reference [RDoc]":http://rdoc.info/projects/floere/view_models
|
35
|
+
"Gem [RubyGems.org]":http://rubygems.org/gems/view_models
|
36
|
+
"Mailing List":http://groups.google.com/group/view_models/topics
|
37
|
+
"Bug Tracker":http://github.com/floere/view_models/issues
|
38
|
+
"Metrics":http://getcaliper.com/caliper/project?repo=git://github.com/floere/view_models.git
|
39
|
+
"Source [Github]":http://github.com/floere/view_models
|
40
|
+
"Homepage":http://floere.github.com/view_models/
|
data/TODO.textile
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
h1. Next up
|
2
2
|
|
3
|
-
*
|
4
|
-
|
3
|
+
* Encapsule the rather bloated hierarchical rendering in a HierarchicalRendering object that mediates the whole rendering process.
|
4
|
+
|
5
|
+
--* Explore possibilities to include Modules in hierarchical rendering.--
|
data/VERSION.yml
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
module ModulesInRenderHierarchy
|
2
|
-
|
2
|
+
|
3
3
|
def self.included klass
|
4
4
|
klass.extend ClassMethods
|
5
5
|
klass.metaclass.alias_method_chain :include, :superclass_override
|
6
6
|
end
|
7
|
-
|
7
|
+
|
8
8
|
module ClassMethods
|
9
9
|
def include_with_superclass_override mod
|
10
10
|
original_superclass = superclass
|
@@ -17,5 +17,5 @@ module ModulesInRenderHierarchy
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
end
|
@@ -4,7 +4,7 @@ module ViewModels
|
|
4
4
|
#
|
5
5
|
module RenderOptions
|
6
6
|
|
7
|
-
#
|
7
|
+
# Base class for Partial and Template.
|
8
8
|
#
|
9
9
|
class Base
|
10
10
|
|
@@ -57,35 +57,43 @@ module ViewModels
|
|
57
57
|
template_name.to_s.include?('/') ? specific_path(template_name) : incomplete_path(template_name)
|
58
58
|
end
|
59
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
|
-
#
|
60
|
+
# Specific path is a specifically named view path, with slashes.
|
72
61
|
#
|
73
62
|
def specific_path name
|
74
63
|
self.path = File.dirname name
|
75
64
|
self.name_with_prefix = File.basename name
|
76
65
|
end
|
77
66
|
|
78
|
-
#
|
67
|
+
# Incomplete path is a view path without slashes. The view models will
|
68
|
+
# try to find out where to take the template from.
|
79
69
|
#
|
80
70
|
def incomplete_path name
|
81
71
|
self.path = nil
|
82
72
|
self.name_with_prefix = name
|
83
73
|
end
|
84
74
|
|
75
|
+
# Add the prefix to the template name.
|
76
|
+
#
|
77
|
+
# Note: Will add an underscore if it's a partial.
|
78
|
+
#
|
85
79
|
def name_with_prefix= name
|
86
80
|
self.name = "#{self.prefix}#{name}"
|
87
81
|
end
|
88
82
|
|
83
|
+
# Extracts the template name from the given parameter.
|
84
|
+
#
|
85
|
+
# If it's a hash, it will remove and use the :partial option.
|
86
|
+
# If not, it will use that.
|
87
|
+
#
|
88
|
+
def deoptionize template_name
|
89
|
+
if template_name.kind_of?(Hash)
|
90
|
+
@options.merge! template_name
|
91
|
+
@options.delete :partial
|
92
|
+
else
|
93
|
+
template_name
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
89
97
|
end
|
90
98
|
|
91
99
|
# A specific container for partial rendering.
|
@@ -32,8 +32,6 @@ describe ViewModels::Helpers::Rails do
|
|
32
32
|
specific_view_model_mapping.should == {}
|
33
33
|
end
|
34
34
|
it "should raise an ArgumentError on an non-mapped model" do
|
35
|
-
# TODO really clear enough that one should provide a ViewModel with an initializer with 2 params?
|
36
|
-
#
|
37
35
|
class SomeViewModelClass; end
|
38
36
|
specific_view_model_mapping[String] = SomeViewModelClass
|
39
37
|
lambda {
|
@@ -43,8 +41,6 @@ describe ViewModels::Helpers::Rails do
|
|
43
41
|
end
|
44
42
|
describe "no specific mapping" do
|
45
43
|
it "should raise on an non-mapped model" do
|
46
|
-
# TODO really clear enough that the view model class is missing?
|
47
|
-
#
|
48
44
|
lambda {
|
49
45
|
view_model_for(42)
|
50
46
|
}.should raise_error(NameError, "uninitialized constant ViewModels::Fixnum")
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 1
|
7
7
|
- 5
|
8
|
-
-
|
9
|
-
version: 1.5.
|
8
|
+
- 7
|
9
|
+
version: 1.5.7
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Florian Hanke
|
@@ -17,7 +17,7 @@ autorequire:
|
|
17
17
|
bindir: bin
|
18
18
|
cert_chain: []
|
19
19
|
|
20
|
-
date: 2010-
|
20
|
+
date: 2010-04-15 00:00:00 +02:00
|
21
21
|
default_executable:
|
22
22
|
dependencies:
|
23
23
|
- !ruby/object:Gem::Dependency
|