cells 2.3.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.
@@ -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