cells 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ class <%= class_name %>Cell < Cell::Base
2
+
3
+ <% for action in actions -%>
4
+ def <%= action %>
5
+ render
6
+ end
7
+
8
+ <% end -%>
9
+ end
@@ -0,0 +1,2 @@
1
+ <h1><%= class_name %>#<%= action %></h1>
2
+ <p>Find me in <%= path %></p>
@@ -0,0 +1,4 @@
1
+ %h1
2
+ <%= class_name %>#<%= action %>
3
+ %p
4
+ Find me in <%= path %>
data/init.rb ADDED
@@ -0,0 +1,59 @@
1
+ # Copyright (c) 2007-2009 Nick Sutterer <apotonick@gmail.com>
2
+ # Copyright (c) 2007-2008 Solide ICT by Peter Bex <peter.bex@solide-ict.nl>
3
+ # and Bob Leers <bleers@fastmail.fm>
4
+ # Some portions and ideas stolen ruthlessly from Ezra Zygmuntowicz <ezmobius@gmail.com>
5
+ #
6
+ # The MIT License
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ # of this software and associated documentation files (the "Software"), to deal
10
+ # in the Software without restriction, including without limitation the rights
11
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ # copies of the Software, and to permit persons to whom the Software is
13
+ # furnished to do so, subject to the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be included in
16
+ # all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ # THE SOFTWARE.
25
+
26
+ # load the baby:
27
+ Cell::Base
28
+ require 'rails_extensions'
29
+
30
+
31
+ ActionController::Base.class_eval do include Cell::ActionController end
32
+ ActionView::Base.class_eval do include Cell::ActionView end
33
+ Cell::Base.class_eval do include Cell::Caching end
34
+
35
+
36
+ ActiveSupport::Dependencies.load_paths << RAILS_ROOT+"/app/cells"
37
+ Cell::Base.add_view_path "app/cells"
38
+ ### DISCUSS: do we need shared layouts for different cells?
39
+ Cell::Base.add_view_path "app/cells/layouts"
40
+
41
+
42
+ # process cells in plugins ("engine-cells").
43
+ # thanks to Tore Torell for making me aware of the initializer instance here:
44
+ config.after_initialize do
45
+ initializer.loaded_plugins.each do |plugin|
46
+ engine_cells_dir = File.join([plugin.directory, "app/cells"])
47
+ next unless plugin.engine?
48
+ next unless File.exists?(engine_cells_dir)
49
+
50
+ # propagate the view- and code path of this engine-cell:
51
+ Cell::Base.view_paths << engine_cells_dir
52
+ ActiveSupport::Dependencies.load_paths << engine_cells_dir
53
+
54
+ # if a path is in +load_once_path+ it won't be reloaded between requests.
55
+ unless config.reload_plugins?
56
+ ActiveSupport::Dependencies.load_once_paths << engine_cells_dir
57
+ end
58
+ end
59
+ end
data/lib/cell/base.rb ADDED
@@ -0,0 +1,454 @@
1
+ module Cell
2
+ # == Basic overview
3
+ #
4
+ # A Cell is the central notion of the cells plugin. A cell acts as a
5
+ # lightweight controller in the sense that it will assign variables and
6
+ # render a view. Cells can be rendered from other cells as well as from
7
+ # regular controllers and views (see ActionView::Base#render_cell and
8
+ # ControllerMethods#render_cell)
9
+ #
10
+ # == A render_cell() cycle
11
+ #
12
+ # A typical <tt>render_cell</tt> state rendering cycle looks like this:
13
+ # render_cell :blog, :newest_article, {...}
14
+ # - an instance of the class <tt>BlogCell</tt> is created, and a hash containing
15
+ # arbitrary parameters is passed
16
+ # - the <em>state method</em> <tt>newest_article</tt> is executed and assigns instance
17
+ # variables to be used in the view
18
+ # - Usually the state method will call #render and return
19
+ # - #render will retrieve the corresponding view
20
+ # (e.g. <tt>app/cells/blog/newest_article.html. [erb|haml|...]</tt>),
21
+ # renders this template and returns the markup.
22
+ #
23
+ # == Design Principles
24
+ # A cell is a completely autonomous object and it should not know or have to know
25
+ # from what controller it is being rendered. For this reason, the controller's
26
+ # instance variables and params hash are not directly available from the cell or
27
+ # its views. This is not a bug, this is a feature! It means cells are truly
28
+ # reusable components which can be plugged in at any point in your application
29
+ # without having to think about what information is available at that point.
30
+ # When rendering a cell, you can explicitly pass variables to the cell in the
31
+ # extra opts argument hash, just like you would pass locals in partials.
32
+ # This hash is then available inside the cell as the @opts instance variable.
33
+ #
34
+ # == Directory hierarchy
35
+ #
36
+ # To get started creating your own cells, you can simply create a new directory
37
+ # structure under your <tt>app</tt> directory called <tt>cells</tt>. Cells are
38
+ # ruby classes which end in the name Cell. So for example, if you have a
39
+ # cell which manages all user information, it would be called <tt>UserCell</tt>.
40
+ # A cell which manages a shopping cart could be called <tt>ShoppingCartCell</tt>.
41
+ #
42
+ # The directory structure of this example would look like this:
43
+ # app/
44
+ # models/
45
+ # ..
46
+ # views/
47
+ # ..
48
+ # helpers/
49
+ # application_helper.rb
50
+ # product_helper.rb
51
+ # ..
52
+ # controllers/
53
+ # ..
54
+ # cells/
55
+ # shopping_cart_cell.rb
56
+ # shopping_cart/
57
+ # status.html.erb
58
+ # product_list.html.erb
59
+ # empty_prompt.html.erb
60
+ # user_cell.rb
61
+ # user/
62
+ # login.html.erb
63
+ # layouts/
64
+ # box.html.erb
65
+ # ..
66
+ #
67
+ # The directory with the same name as the cell contains views for the
68
+ # cell's <em>states</em>. A state is an executed method along with a
69
+ # rendered view, resulting in content. This means that states are to
70
+ # cells as actions are to controllers, so each state has its own view.
71
+ # The use of partials is deprecated with cells, it is better to just
72
+ # render a different state on the same cell (which also works recursively).
73
+ #
74
+ # Anyway, <tt>render :partial </tt> in a cell view will work, if the
75
+ # partial is contained in the cell's view directory.
76
+ #
77
+ # As can be seen above, Cells also can make use of helpers. All Cells
78
+ # include ApplicationHelper by default, but you can add additional helpers
79
+ # as well with the Cell::Base.helper class method:
80
+ # class ShoppingCartCell < Cell::Base
81
+ # helper :product
82
+ # ...
83
+ # end
84
+ #
85
+ # This will make the <tt>ProductHelper</tt> from <tt>app/helpers/product_helper.rb</tt>
86
+ # available from all state views from our <tt>ShoppingCartCell</tt>.
87
+ #
88
+ # == Cell inheritance
89
+ #
90
+ # Unlike controllers, Cells can form a class hierarchy. When a cell class
91
+ # is inherited by another cell class, its states are inherited as regular
92
+ # methods are, but also its views are inherited. Whenever a view is looked up,
93
+ # the view finder first looks for a file in the directory belonging to the
94
+ # current cell class, but if this is not found in the application or any
95
+ # engine, the superclass' directory is checked. This continues all the
96
+ # way up until it stops at Cell::Base.
97
+ #
98
+ # For instance, when you have two cells:
99
+ # class MenuCell < Cell::Base
100
+ # def show
101
+ # end
102
+ #
103
+ # def edit
104
+ # end
105
+ # end
106
+ #
107
+ # class MainMenuCell < MenuCell
108
+ # .. # no need to redefine show/edit if they do the same!
109
+ # end
110
+ # and the following directory structure in <tt>app/cells</tt>:
111
+ # app/cells/
112
+ # menu/
113
+ # show.html.erb
114
+ # edit.html.erb
115
+ # main_menu/
116
+ # show.html.erb
117
+ # then when you call
118
+ # render_cell :main_menu, :show
119
+ # the main menu specific show.html.erb (<tt>app/cells/main_menu/show.html.erb</tt>)
120
+ # is rendered, but when you call
121
+ # render_cell :main_menu, :edit
122
+ # cells notices that the main menu does not have a specific view for the
123
+ # <tt>edit</tt> state, so it will render the view for the parent class,
124
+ # <tt>app/cells/menu/edit.html.erb</tt>
125
+ #
126
+ #
127
+ # == Gettext support
128
+ #
129
+ # Cells support gettext, just name your views accordingly. It works exactly equivalent
130
+ # to controller views.
131
+ #
132
+ # cells/user/user_form.html.erb
133
+ # cells/user/user_form_de.html.erb
134
+ #
135
+ # If gettext is set to DE_de, the latter view will be chosen.
136
+ class Base
137
+ include ActionController::Helpers
138
+ include ActionController::RequestForgeryProtection
139
+
140
+ helper ApplicationHelper
141
+
142
+
143
+ class << self
144
+ attr_accessor :request_forgery_protection_token
145
+
146
+ # A template file will be looked for in each view path. This is typically
147
+ # just RAILS_ROOT/app/cells, but you might want to add e.g.
148
+ # RAILS_ROOT/app/views.
149
+ def add_view_path(path)
150
+ self.view_paths << RAILS_ROOT + '/' + path
151
+ end
152
+
153
+ # Creates a cell instance of the class <tt>name</tt>Cell, passing through
154
+ # <tt>opts</tt>.
155
+ def create_cell_for(controller, name, opts={})
156
+ class_from_cell_name(name).new(controller, opts)
157
+ end
158
+
159
+ # Declare a controller method as a helper. For example,
160
+ # helper_method :link_to
161
+ # def link_to(name, options) ... end
162
+ # makes the link_to controller method available in the view.
163
+ def helper_method(*methods)
164
+ methods.flatten.each do |method|
165
+ master_helper_module.module_eval <<-end_eval
166
+ def #{method}(*args, &block)
167
+ @cell.send(%(#{method}), *args, &block)
168
+ end
169
+ end_eval
170
+ end
171
+ end
172
+
173
+ # Return the default view for the given state on this cell subclass.
174
+ # This is a file with the name of the state under a directory with the
175
+ # name of the cell followed by a template extension.
176
+ def view_for_state(state)
177
+ "#{cell_name}/#{state}"
178
+ end
179
+
180
+ # Find a possible template for a cell's current state. It tries to find a
181
+ # template file with the name of the state under a subdirectory
182
+ # with the name of the cell under the <tt>app/cells</tt> directory.
183
+ # If this file cannot be found, it will try to call this method on
184
+ # the superclass. This way you only have to write a state template
185
+ # once when a more specific cell does not need to change anything in
186
+ # that view.
187
+ def find_class_view_for_state(state)
188
+ return [view_for_state(state)] if superclass == Cell::Base
189
+
190
+ superclass.find_class_view_for_state(state) << view_for_state(state)
191
+ end
192
+
193
+ # Get the name of this cell's class as an underscored string,
194
+ # with _cell removed.
195
+ #
196
+ # Example:
197
+ # UserCell.cell_name
198
+ # => "user"
199
+ def cell_name
200
+ name.underscore.sub(/_cell/, '')
201
+ end
202
+
203
+ # Given a cell name, finds the class that belongs to it.
204
+ #
205
+ # Example:
206
+ # Cell::Base.class_from_cell_name(:user)
207
+ # => UserCell
208
+ def class_from_cell_name(cell_name)
209
+ "#{cell_name}_cell".classify.constantize
210
+ end
211
+
212
+ def state2view_cache
213
+ @state2view_cache ||= {}
214
+ end
215
+
216
+ def cache_configured?; ::ActionController::Base.cache_configured?; end
217
+ end
218
+
219
+ class_inheritable_array :view_paths, :instance_writer => false
220
+ self.view_paths = ActionView::PathSet.new
221
+
222
+ class_inheritable_accessor :allow_forgery_protection
223
+ self.allow_forgery_protection = true
224
+
225
+ class_inheritable_accessor :default_template_format
226
+ self.default_template_format = :html
227
+
228
+
229
+ delegate :params, :session, :request, :logger, :to => :controller
230
+
231
+
232
+ attr_accessor :controller
233
+ attr_reader :state_name
234
+
235
+
236
+ def initialize(controller, options={})
237
+ @controller = controller
238
+ @opts = options
239
+ end
240
+
241
+ def cell_name
242
+ self.class.cell_name
243
+ end
244
+
245
+
246
+ # Render the given state. You can pass the name as either a symbol or
247
+ # a string.
248
+ def render_state(state)
249
+ @cell = self
250
+ @state_name = state
251
+
252
+ content = dispatch_state(state)
253
+
254
+ return content if content.kind_of? String
255
+
256
+ render_view_for_backward_compat(content, state)
257
+ end
258
+
259
+ # Call the state method.
260
+ def dispatch_state(state)
261
+ send(state)
262
+ end
263
+
264
+ # We will soon remove the implicit call to render_view_for, but here it is for your convenience.
265
+ def render_view_for_backward_compat(opts, state)
266
+ ActiveSupport::Deprecation.warn "You either didn't call #render or forgot to return a string in the state method '#{state}'. However, returning nil is deprecated for the sake of explicitness"
267
+
268
+ render_view_for(opts, state)
269
+ end
270
+
271
+
272
+ # Renders the view for the current state and returns the markup for the component.
273
+ # Usually called and returned at the end of a state method.
274
+ #
275
+ # ==== Options
276
+ # * <tt>:view</tt> - Specifies the name of the view file to render. Defaults to the current state name.
277
+ # * <tt>:template_format</tt> - Allows using a format different to <tt>:html</tt>.
278
+ # * <tt>:layout</tt> - If set to a valid filename inside your cell's view_paths, the current state view will be rendered inside the layout (as known from controller actions). Layouts should reside in <tt>app/cells/layouts</tt>.
279
+ # * <tt>:locals</tt> - Makes the named parameters available as variables in the view.
280
+ # * <tt>:text</tt> - Just renders plain text.
281
+ # * <tt>:inline</tt> - Renders an inline template as state view. See ActionView::Base#render for details.
282
+ # * <tt>:file</tt> - Specifies the name of the file template to render.
283
+ # * <tt>:nothing</tt> - Will make the component kinda invisible and doesn't invoke the rendering cycle.
284
+ # * <tt>:state</tt> - Instantly invokes another rendering cycle for the passed state and returns.
285
+ # Example:
286
+ # class MyCell < Cell::Base
287
+ # def my_first_state
288
+ # # ... do something
289
+ # render
290
+ # end
291
+ #
292
+ # will just render the view <tt>my_first_state.html</tt>.
293
+ #
294
+ # def my_first_state
295
+ # # ... do something
296
+ # render :view => :my_first_state, :layout => "metal"
297
+ # end
298
+ #
299
+ # will also use the view <tt>my_first_state.html</tt> as template and even put it in the layout
300
+ # <tt>metal</tt> that's located at <tt>$RAILS_ROOT/app/cells/layouts/metal.html.erb</tt>.
301
+ #
302
+ # def say_your_name
303
+ # render :locals => {:name => "Nick"}
304
+ # end
305
+ #
306
+ # will make the variable +name+ available in the view <tt>say_your_name.html</tt>.
307
+ #
308
+ # def say_your_name
309
+ # render :nothing => true
310
+ # end
311
+ #
312
+ # will render an empty string thus keeping your name a secret.
313
+ #
314
+ #
315
+ # ==== Where have all the partials gone?
316
+ # In Cells we abandoned the term 'partial' in favor of plain 'views' - we don't need to distinguish
317
+ # between both terms. A cell view is both, a view and a kind of partial as it represents only a small
318
+ # part of the page.
319
+ # Just use <tt>:view</tt> and enjoy.
320
+ def render(opts={})
321
+ render_view_for(opts, @state_name) ### FIXME: i don't like the magic access to @state_name here. ugly!
322
+ end
323
+
324
+ # Render the view belonging to the given state. Will raise ActionView::MissingTemplate
325
+ # if it can not find one of the requested view template. Note that this behaviour was
326
+ # introduced in cells 2.3 and replaces the former warning message.
327
+ def render_view_for(opts, state)
328
+ return "" if opts[:nothing]
329
+
330
+ action_view = setup_action_view
331
+
332
+ ### TODO: dispatch dynamically:
333
+ if opts[:text]
334
+ elsif opts[:inline]
335
+ elsif opts[:file]
336
+ elsif opts[:state]
337
+ opts[:text] = render_state(opts[:state])
338
+ else
339
+ # handle :layout, :template_format, :view
340
+ opts = defaultize_render_options_for(opts, state)
341
+
342
+ # set instance vars, include helpers:
343
+ prepare_action_view_for(action_view, opts)
344
+
345
+ template = find_family_view_for_state_with_caching(opts[:view], action_view)
346
+ opts[:file] = template
347
+ end
348
+
349
+ opts = sanitize_render_options(opts)
350
+
351
+ action_view.render_for(opts)
352
+ end
353
+
354
+
355
+ # Defaultize the passed options from #render.
356
+ def defaultize_render_options_for(opts, state)
357
+ opts[:template_format] ||= self.class.default_template_format
358
+ opts[:view] ||= state
359
+ opts
360
+ end
361
+
362
+ def prepare_action_view_for(action_view, opts)
363
+ # make helpers available:
364
+ include_helpers_in_class(action_view.class)
365
+
366
+ action_view.assigns = assigns_for_view # make instance vars available.
367
+ action_view.template_format = opts[:template_format]
368
+ end
369
+
370
+ def setup_action_view
371
+ view_class = Class.new(Cell::View)
372
+ action_view = view_class.new(self.class.view_paths, {}, @controller)
373
+ action_view.cell = self
374
+ action_view
375
+ end
376
+
377
+ # Prepares <tt>opts</tt> to be passed to ActionView::Base#render by removing
378
+ # unknown parameters.
379
+ def sanitize_render_options(opts)
380
+ opts.except!(:view, :state)
381
+ end
382
+
383
+
384
+ # Climbs up the inheritance hierarchy of the Cell, looking for a view
385
+ # for the current <tt>state</tt> in each level.
386
+ # As soon as a view file is found it is returned as an ActionView::Template
387
+ # instance.
388
+ ### DISCUSS: moved to Cell::View#find_template in rainhead's fork:
389
+ def find_family_view_for_state(state, action_view)
390
+ missing_template_exception = nil
391
+
392
+ possible_paths_for_state(state).each do |template_path|
393
+ # we need to catch MissingTemplate, since we want to try for all possible
394
+ # family views.
395
+ begin
396
+ if view = action_view.try_picking_template_for_path(template_path)
397
+ return view
398
+ end
399
+ rescue ::ActionView::MissingTemplate => missing_template_exception
400
+ end
401
+ end
402
+
403
+ raise missing_template_exception
404
+ end
405
+
406
+ # In production mode, the view for a state/template_format is cached.
407
+ ### DISCUSS: ActionView::Base already caches results for #pick_template, so maybe
408
+ ### we should just cache the family path for a state/format?
409
+ def find_family_view_for_state_with_caching(state, action_view)
410
+ return find_family_view_for_state(state, action_view) unless self.class.cache_configured?
411
+
412
+ # in production mode:
413
+ key = "#{state}/#{action_view.template_format}"
414
+ state2view = self.class.state2view_cache
415
+ state2view[key] || state2view[key] = find_family_view_for_state(state, action_view)
416
+ end
417
+
418
+ # Find possible files that belong to the state. This first tries the cell's
419
+ # <tt>#view_for_state</tt> method and if that returns a true value, it
420
+ # will accept that value as a string and interpret it as a pathname for
421
+ # the view file. If it returns a falsy value, it will call the Cell's class
422
+ # method find_class_view_for_state to determine the file to check.
423
+ #
424
+ # You can override the Cell::Base#view_for_state method for a particular
425
+ # cell if you wish to make it decide dynamically what file to render.
426
+ def possible_paths_for_state(state)
427
+ self.class.find_class_view_for_state(state).reverse!
428
+ end
429
+
430
+ # Prepares the hash {instance_var => value, ...} that should be available
431
+ # in the ActionView when rendering the state view.
432
+ def assigns_for_view
433
+ assigns = {}
434
+ (self.instance_variables - ivars_to_ignore).each do |k|
435
+ assigns[k[1..-1]] = instance_variable_get(k)
436
+ end
437
+ assigns
438
+ end
439
+
440
+
441
+ # When passed a copy of the ActionView::Base class, it
442
+ # will mix in all helper classes for this cell in that class.
443
+ def include_helpers_in_class(view_klass)
444
+ view_klass.send(:include, self.class.master_helper_module)
445
+ end
446
+
447
+
448
+ # Defines the instance variables that should <em>not</em> be copied to the
449
+ # View instance.
450
+ def ivars_to_ignore; ['@controller']; end
451
+
452
+
453
+ end
454
+ end