cells 2.3.0 → 3.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.
Files changed (52) hide show
  1. data/CHANGES +3 -3
  2. data/MIT-LICENSE +22 -0
  3. data/README.rdoc +2 -2
  4. data/Rakefile +22 -25
  5. data/generators/cell/templates/cell.rb +1 -1
  6. data/generators/cells_install/USAGE +3 -0
  7. data/generators/cells_install/cells_install_generator.rb +12 -0
  8. data/generators/cells_install/templates/initializer.rb +9 -0
  9. data/lib/cell.rb +9 -0
  10. data/lib/cells.rb +68 -0
  11. data/lib/cells/cell.rb +15 -0
  12. data/lib/cells/cell/base.rb +461 -0
  13. data/lib/cells/cell/caching.rb +163 -0
  14. data/lib/cells/cell/view.rb +56 -0
  15. data/lib/cells/helpers.rb +7 -0
  16. data/lib/cells/helpers/capture_helper.rb +51 -0
  17. data/lib/cells/rails.rb +17 -0
  18. data/lib/cells/rails/action_controller.rb +37 -0
  19. data/lib/cells/rails/action_view.rb +37 -0
  20. data/lib/cells/version.rb +5 -0
  21. data/rails/init.rb +30 -0
  22. data/test/{cells → app/cells}/cells_test_one_cell.rb +2 -2
  23. data/test/{cells → app/cells}/cells_test_two_cell.rb +2 -0
  24. data/test/{cells → app/cells}/really_module/nested_cell.rb +1 -1
  25. data/test/app/cells/simple_cell.rb +7 -0
  26. data/test/{cells → app/cells}/test_cell.rb +3 -7
  27. data/test/app/controllers/cells_test_controller.rb +44 -0
  28. data/test/app/helpers/application_helper.rb +7 -0
  29. data/test/{helpers → app/helpers}/helper_using_cell_helper.rb +3 -1
  30. data/test/bugs_test.rb +10 -13
  31. data/test/caching_test.rb +169 -165
  32. data/test/capture_helper_test.rb +59 -0
  33. data/test/cells_test.rb +160 -158
  34. data/test/helper_test.rb +83 -104
  35. data/test/rails_test.rb +35 -0
  36. data/test/render_test.rb +163 -106
  37. data/test/support/assertions_helper.rb +60 -0
  38. data/test/test_helper.rb +67 -0
  39. metadata +35 -25
  40. data/README +0 -150
  41. data/VERSION +0 -1
  42. data/init.rb +0 -59
  43. data/lib/cell/base.rb +0 -454
  44. data/lib/cell/caching.rb +0 -151
  45. data/lib/cell/view.rb +0 -55
  46. data/lib/cells_helper.rb +0 -49
  47. data/lib/rails_extensions.rb +0 -75
  48. data/test/capture_test.rb +0 -56
  49. data/test/cell_view_test.rb +0 -9
  50. data/test/cells/simple_cell.rb +0 -5
  51. data/test/rails_extensions_test.rb +0 -25
  52. data/test/testing_helper.rb +0 -67
data/CHANGES CHANGED
@@ -1,8 +1,8 @@
1
1
  - 2.3
2
- * Cell::Base#new(controller, opts={})
2
+ * ::Cell::Base#new(controller, opts={})
3
3
  We got rid of the second argument cell_name, since it was completely useless.
4
4
  * when a state view couldn't be found there's no longer a warning message, but an exception.
5
- * moved Cell::Base to lib/cell/base.rb
5
+ * moved ::Cell::Base to lib/cell/base.rb
6
6
  * moved Rails extension code to lib/rails_extensions.rb
7
7
  * removed all the boot code since we don't need it anymore
8
8
 
@@ -16,7 +16,7 @@
16
16
  that fixes bug #1
17
17
  * introduced view inheritance, so derived cells inherit view files from their
18
18
  superclass
19
- * introduced automatic view file finding, Cell::Base#path is no longer needed
19
+ * introduced automatic view file finding, ::Cell::Base#path is no longer needed
20
20
  * added support for helpers in cell views
21
21
  * removed Cell::Registry in favor or a new cells autoloading mechanism
22
22
 
data/MIT-LICENSE ADDED
@@ -0,0 +1,22 @@
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
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
data/README.rdoc CHANGED
@@ -19,7 +19,7 @@ To quickly create the necessary files for an example cell run the generator:
19
19
  The generated cell class located in <tt>app/cells/article_cell.rb</tt> could look like
20
20
  this, after some editing:
21
21
 
22
- class ArticleCell < Cell::Base
22
+ class ArticleCell < ::Cell::Base
23
23
  helper :my_formatting_and_escaping_helper # you can use helpers in cell views!
24
24
 
25
25
  def newest
@@ -116,7 +116,7 @@ This release is tested and runs with Rails 2.3.
116
116
 
117
117
  = Documentation
118
118
 
119
- Reference documentation is found in the documentation of the Cell::Base class.
119
+ Reference documentation is found in the documentation of the ::Cell::Base class.
120
120
 
121
121
  See http://cells.rubyforge.org for documentation targeted at cells
122
122
  newbies, including an overview of what you can do with cells and a
data/Rakefile CHANGED
@@ -1,22 +1,18 @@
1
+ # encoding: utf-8
1
2
  require 'rake'
2
3
  require 'rake/testtask'
3
4
  require 'rake/rdoctask'
5
+ require File.join(File.dirname(__FILE__), 'lib', 'cells', 'version')
4
6
 
5
- NAME = "cells"
6
- SUMMARY = %{Cells are lightweight controllers for Rails and can be rendered in controllers and views, providing an elegant and fast way for encapsulation and component-orientation.}
7
- HOMEPAGE = "http://cells.rubyforge.org"
8
- AUTHORS = ["Nick Sutterer"]
9
- EMAIL = "apotonick@gmail.com"
10
- SUPPORT_FILES = %w[README CHANGES]
11
7
 
12
8
  desc 'Default: run unit tests.'
13
9
  task :default => :test
14
10
 
15
11
  desc 'Test the cells plugin.'
16
- Rake::TestTask.new(:test) do |t|
17
- t.libs << 'lib'
18
- t.pattern = 'test/**/*_test.rb'
19
- t.verbose = true
12
+ Rake::TestTask.new(:test) do |test|
13
+ test.libs << 'test'
14
+ test.pattern = 'test/**/*_test.rb'
15
+ test.verbose = true
20
16
  end
21
17
 
22
18
  desc 'Generate documentation for the cells plugin.'
@@ -24,7 +20,7 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
24
20
  rdoc.rdoc_dir = 'rdoc'
25
21
  rdoc.title = 'Cells Documentation'
26
22
  rdoc.options << '--line-numbers' << '--inline-source'
27
- rdoc.rdoc_files.include('README')
23
+ rdoc.rdoc_files.include('README.rdoc')
28
24
  rdoc.rdoc_files.include('init.rb')
29
25
  rdoc.rdoc_files.include('lib/**/*.rb')
30
26
  end
@@ -56,22 +52,23 @@ end
56
52
  begin
57
53
  gem 'jeweler'
58
54
  require 'jeweler'
59
- Jeweler::Tasks.new do |gemspec|
60
- gemspec.name = NAME
61
- gemspec.summary = SUMMARY
62
- gemspec.description = SUMMARY
63
- gemspec.homepage = HOMEPAGE
64
- gemspec.authors = AUTHORS
65
- gemspec.email = EMAIL
66
-
67
- gemspec.require_paths = %w{lib}
68
- gemspec.files = FileList["[A-Z]*", File.join(*%w[{generators,lib} ** *]).to_s, "init.rb"]
69
- gemspec.extra_rdoc_files = SUPPORT_FILES
70
-
71
- # gemspec.add_dependency 'activesupport', '>= 2.3.0' # Dependencies and minimum versions?
55
+
56
+ Jeweler::Tasks.new do |spec|
57
+ spec.name = "cells"
58
+ spec.version = ::Cells::VERSION
59
+ spec.summary = %{Cells are lightweight controllers for Rails and can be rendered in controllers and views, providing an elegant and fast way for encapsulation and component-orientation.}
60
+ spec.description = spec.summary
61
+ spec.homepage = "http://cells.rubyforge.org"
62
+ spec.authors = ["Nick Sutterer"]
63
+ spec.email = "apotonick@gmail.com"
64
+
65
+ spec.files = FileList["[A-Z]*", File.join(*%w[{generators,lib,rails} ** *]).to_s]
66
+
67
+ # spec.add_dependency 'activesupport', '>= 2.3.0' # Dependencies and minimum versions?
72
68
  end
69
+
73
70
  Jeweler::GemcutterTasks.new
74
71
  rescue LoadError
75
72
  puts "Jeweler - or one of its dependencies - is not available. " <<
76
- "Install it with: sudo gem install jeweler -s http://gemcutter.org"
73
+ "Install it with: sudo gem install jeweler -s http://gemcutter.org"
77
74
  end
@@ -1,4 +1,4 @@
1
- class <%= class_name %>Cell < Cell::Base
1
+ class <%= class_name %>Cell < ::Cell::Base
2
2
 
3
3
  <% for action in actions -%>
4
4
  def <%= action %>
@@ -0,0 +1,3 @@
1
+ To copy a Cells initializer to your current Rails app - with some configuration values - just do:
2
+
3
+ script/generate cells_install
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+
3
+ class CellsInstallGenerator < Rails::Generator::Base
4
+
5
+ def manifest
6
+ record do |m|
7
+ m.directory File.join('config', 'initializers')
8
+ m.template 'initializer.rb', File.join('config', 'initializers', 'cells.rb')
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ if defined?(Cells)
4
+ # Use this hook to configure Cells.
5
+ Cells.setup do |config|
6
+ # Configure view paths that Cells should scan for cell views.
7
+ # config.view_paths << Rails.root.join('app', 'views')
8
+ end
9
+ end
data/lib/cell.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'cells'
2
+
3
+ # Make cell class interface leaner, i.e. ::Cell::Base < ::Cells::Cell::Base, etc.
4
+ # Note: Reason for doing like so is to make load path-resolving complexity to a minimum.
5
+ #
6
+ module Cell
7
+ Base = ::Cells::Cell::Base
8
+ View = ::Cells::Cell::View
9
+ end
data/lib/cells.rb ADDED
@@ -0,0 +1,68 @@
1
+ # encoding: utf-8
2
+
3
+ begin
4
+ require 'active_support'
5
+ rescue
6
+ gem 'activesupport'
7
+ require 'active_support'
8
+ end
9
+
10
+ begin
11
+ require 'action_controller'
12
+ rescue
13
+ gem 'actionpack'
14
+ require 'action_controller'
15
+ end
16
+
17
+ begin
18
+ require 'action_view'
19
+ rescue
20
+ gem 'actionpack'
21
+ require 'action_view'
22
+ end
23
+
24
+ require 'cells/cell'
25
+ require 'cells/helpers'
26
+ require 'cell'
27
+
28
+ module Cells
29
+ # Any config should be placed here using +mattr_accessor+.
30
+
31
+ # Default view paths for Cells.
32
+ DEFAULT_VIEW_PATHS = [
33
+ File.join('app', 'cells'),
34
+ File.join('app', 'cells', 'layouts')
35
+ ]
36
+
37
+ class << self
38
+ # Holds paths in which Cells should look for cell views (i.e. view template files).
39
+ #
40
+ # == Default:
41
+ #
42
+ # * +app/cells+
43
+ # * +app/cells/layouts+
44
+ #
45
+ def self.view_paths
46
+ ::Cell::Base.view_paths
47
+ end
48
+ def self.view_paths=(paths)
49
+ ::Cell::Base.view_paths = paths
50
+ end
51
+ end
52
+
53
+ # Cells setup/configuration helper for initializer.
54
+ #
55
+ # == Usage/Exmaples:
56
+ #
57
+ # Cells.setup do |config|
58
+ # config.cell_view_paths << Rails.root.join('lib', 'cells')
59
+ # end
60
+ #
61
+ def self.setup
62
+ yield(self)
63
+ end
64
+ end
65
+
66
+ Cell::Base.view_paths = Cells::DEFAULT_VIEW_PATHS if Cell::Base.view_paths.blank?
67
+
68
+ require 'cells/rails'
data/lib/cells/cell.rb ADDED
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ module Cells
4
+ module Cell
5
+ autoload :Base, 'cells/cell/base'
6
+ autoload :View, 'cells/cell/view'
7
+ autoload :Caching, 'cells/cell/caching'
8
+ end
9
+ end
10
+
11
+ # Mixin caching behaviour into +::Cell::Base+.
12
+ # Note: Must be done using class_eval.
13
+ Cells::Cell::Base.class_eval do
14
+ include ::Cells::Cell::Caching
15
+ end
@@ -0,0 +1,461 @@
1
+ # encoding: utf-8
2
+ require 'action_controller/base'
3
+
4
+ module Cells
5
+ module Cell
6
+ # == Basic overview
7
+ #
8
+ # A Cell is the central notion of the cells plugin. A cell acts as a
9
+ # lightweight controller in the sense that it will assign variables and
10
+ # render a view. Cells can be rendered from other cells as well as from
11
+ # regular controllers and views (see ActionView::Base#render_cell and
12
+ # ControllerMethods#render_cell)
13
+ #
14
+ # == A render_cell() cycle
15
+ #
16
+ # A typical <tt>render_cell</tt> state rendering cycle looks like this:
17
+ # render_cell :blog, :newest_article, {...}
18
+ # - an instance of the class <tt>BlogCell</tt> is created, and a hash containing
19
+ # arbitrary parameters is passed
20
+ # - the <em>state method</em> <tt>newest_article</tt> is executed and assigns instance
21
+ # variables to be used in the view
22
+ # - Usually the state method will call #render and return
23
+ # - #render will retrieve the corresponding view
24
+ # (e.g. <tt>app/cells/blog/newest_article.html. [erb|haml|...]</tt>),
25
+ # renders this template and returns the markup.
26
+ #
27
+ # == Design Principles
28
+ # A cell is a completely autonomous object and it should not know or have to know
29
+ # from what controller it is being rendered. For this reason, the controller's
30
+ # instance variables and params hash are not directly available from the cell or
31
+ # its views. This is not a bug, this is a feature! It means cells are truly
32
+ # reusable components which can be plugged in at any point in your application
33
+ # without having to think about what information is available at that point.
34
+ # When rendering a cell, you can explicitly pass variables to the cell in the
35
+ # extra opts argument hash, just like you would pass locals in partials.
36
+ # This hash is then available inside the cell as the @opts instance variable.
37
+ #
38
+ # == Directory hierarchy
39
+ #
40
+ # To get started creating your own cells, you can simply create a new directory
41
+ # structure under your <tt>app</tt> directory called <tt>cells</tt>. Cells are
42
+ # ruby classes which end in the name Cell. So for example, if you have a
43
+ # cell which manages all user information, it would be called <tt>UserCell</tt>.
44
+ # A cell which manages a shopping cart could be called <tt>ShoppingCartCell</tt>.
45
+ #
46
+ # The directory structure of this example would look like this:
47
+ # app/
48
+ # models/
49
+ # ..
50
+ # views/
51
+ # ..
52
+ # helpers/
53
+ # application_helper.rb
54
+ # product_helper.rb
55
+ # ..
56
+ # controllers/
57
+ # ..
58
+ # cells/
59
+ # shopping_cart_cell.rb
60
+ # shopping_cart/
61
+ # status.html.erb
62
+ # product_list.html.erb
63
+ # empty_prompt.html.erb
64
+ # user_cell.rb
65
+ # user/
66
+ # login.html.erb
67
+ # layouts/
68
+ # box.html.erb
69
+ # ..
70
+ #
71
+ # The directory with the same name as the cell contains views for the
72
+ # cell's <em>states</em>. A state is an executed method along with a
73
+ # rendered view, resulting in content. This means that states are to
74
+ # cells as actions are to controllers, so each state has its own view.
75
+ # The use of partials is deprecated with cells, it is better to just
76
+ # render a different state on the same cell (which also works recursively).
77
+ #
78
+ # Anyway, <tt>render :partial </tt> in a cell view will work, if the
79
+ # partial is contained in the cell's view directory.
80
+ #
81
+ # As can be seen above, Cells also can make use of helpers. All Cells
82
+ # include ApplicationHelper by default, but you can add additional helpers
83
+ # as well with the ::Cell::Base.helper class method:
84
+ # class ShoppingCartCell < ::Cell::Base
85
+ # helper :product
86
+ # ...
87
+ # end
88
+ #
89
+ # This will make the <tt>ProductHelper</tt> from <tt>app/helpers/product_helper.rb</tt>
90
+ # available from all state views from our <tt>ShoppingCartCell</tt>.
91
+ #
92
+ # == Cell inheritance
93
+ #
94
+ # Unlike controllers, Cells can form a class hierarchy. When a cell class
95
+ # is inherited by another cell class, its states are inherited as regular
96
+ # methods are, but also its views are inherited. Whenever a view is looked up,
97
+ # the view finder first looks for a file in the directory belonging to the
98
+ # current cell class, but if this is not found in the application or any
99
+ # engine, the superclass' directory is checked. This continues all the
100
+ # way up until it stops at ::Cell::Base.
101
+ #
102
+ # For instance, when you have two cells:
103
+ # class MenuCell < ::Cell::Base
104
+ # def show
105
+ # end
106
+ #
107
+ # def edit
108
+ # end
109
+ # end
110
+ #
111
+ # class MainMenuCell < MenuCell
112
+ # .. # no need to redefine show/edit if they do the same!
113
+ # end
114
+ # and the following directory structure in <tt>app/cells</tt>:
115
+ # app/cells/
116
+ # menu/
117
+ # show.html.erb
118
+ # edit.html.erb
119
+ # main_menu/
120
+ # show.html.erb
121
+ # then when you call
122
+ # render_cell :main_menu, :show
123
+ # the main menu specific show.html.erb (<tt>app/cells/main_menu/show.html.erb</tt>)
124
+ # is rendered, but when you call
125
+ # render_cell :main_menu, :edit
126
+ # cells notices that the main menu does not have a specific view for the
127
+ # <tt>edit</tt> state, so it will render the view for the parent class,
128
+ # <tt>app/cells/menu/edit.html.erb</tt>
129
+ #
130
+ #
131
+ # == Gettext support
132
+ #
133
+ # Cells support gettext, just name your views accordingly. It works exactly equivalent
134
+ # to controller views.
135
+ #
136
+ # cells/user/user_form.html.erb
137
+ # cells/user/user_form_de.html.erb
138
+ #
139
+ # If gettext is set to DE_de, the latter view will be chosen.
140
+ class Base
141
+ include ::ActionController::Helpers
142
+ include ::ActionController::RequestForgeryProtection
143
+
144
+ class_inheritable_array :view_paths, :instance_writer => false
145
+ write_inheritable_attribute(:view_paths, ActionView::PathSet.new) # Force use of a PathSet in this attribute, self.view_paths = ActionView::PathSet.new would still yield in an array
146
+
147
+ class << self
148
+ attr_accessor :request_forgery_protection_token
149
+
150
+ # Use this if you want Cells to look up view templates
151
+ # in directories other than the default.
152
+ def view_paths=(paths)
153
+ self.view_paths.clear.concat(paths) # don't let 'em overwrite the PathSet.
154
+ end
155
+
156
+ # A template file will be looked for in each view path. This is typically
157
+ # just RAILS_ROOT/app/cells, but you might want to add e.g.
158
+ # RAILS_ROOT/app/views.
159
+ def add_view_path(path)
160
+ path = ::Rails.root.join(path) if defined?(::Rails)
161
+ self.view_paths << path unless self.view_paths.include?(path)
162
+ end
163
+
164
+ # Creates a cell instance of the class <tt>name</tt>Cell, passing through
165
+ # <tt>opts</tt>.
166
+ def create_cell_for(controller, name, opts={})
167
+ class_from_cell_name(name).new(controller, opts)
168
+ end
169
+
170
+ # Declare a controller method as a helper. For example,
171
+ # helper_method :link_to
172
+ # def link_to(name, options) ... end
173
+ # makes the link_to controller method available in the view.
174
+ def helper_method(*methods)
175
+ methods.flatten.each do |method|
176
+ master_helper_module.module_eval <<-end_eval
177
+ def #{method}(*args, &block)
178
+ @cell.send(:#{method}, *args, &block)
179
+ end
180
+ end_eval
181
+ end
182
+ end
183
+
184
+ # Return the default view for the given state on this cell subclass.
185
+ # This is a file with the name of the state under a directory with the
186
+ # name of the cell followed by a template extension.
187
+ def view_for_state(state)
188
+ "#{cell_name}/#{state}"
189
+ end
190
+
191
+ # Find a possible template for a cell's current state. It tries to find a
192
+ # template file with the name of the state under a subdirectory
193
+ # with the name of the cell under the <tt>app/cells</tt> directory.
194
+ # If this file cannot be found, it will try to call this method on
195
+ # the superclass. This way you only have to write a state template
196
+ # once when a more specific cell does not need to change anything in
197
+ # that view.
198
+ def find_class_view_for_state(state)
199
+ return [view_for_state(state)] if superclass == ::Cell::Base
200
+
201
+ superclass.find_class_view_for_state(state) << view_for_state(state)
202
+ end
203
+
204
+ # Get the name of this cell's class as an underscored string,
205
+ # with _cell removed.
206
+ #
207
+ # Example:
208
+ # UserCell.cell_name
209
+ # => "user"
210
+ def cell_name
211
+ name.underscore.sub(/_cell/, '')
212
+ end
213
+
214
+ # Given a cell name, finds the class that belongs to it.
215
+ #
216
+ # Example:
217
+ # ::Cell::Base.class_from_cell_name(:user)
218
+ # => UserCell
219
+ def class_from_cell_name(cell_name)
220
+ "#{cell_name}_cell".classify.constantize
221
+ end
222
+
223
+ def state2view_cache
224
+ @state2view_cache ||= {}
225
+ end
226
+
227
+ def cache_configured?
228
+ ::ActionController::Base.cache_configured?
229
+ end
230
+ end
231
+
232
+
233
+ class_inheritable_accessor :allow_forgery_protection
234
+ self.allow_forgery_protection = true
235
+
236
+ class_inheritable_accessor :default_template_format
237
+ self.default_template_format = :html
238
+
239
+ delegate :params, :session, :request, :logger, :to => :controller
240
+
241
+ attr_accessor :controller
242
+ attr_reader :state_name
243
+
244
+ def initialize(controller, options={})
245
+ @controller = controller
246
+ @opts = options
247
+ end
248
+
249
+ def cell_name
250
+ self.class.cell_name
251
+ end
252
+
253
+ # Render the given state. You can pass the name as either a symbol or
254
+ # a string.
255
+ def render_state(state)
256
+ @cell = self
257
+ @state_name = state
258
+
259
+ content = dispatch_state(state)
260
+
261
+ return content if content.kind_of? String
262
+
263
+ render_view_for_backward_compat(content, state)
264
+ end
265
+
266
+ # Call the state method.
267
+ def dispatch_state(state)
268
+ send(state)
269
+ end
270
+
271
+ # We will soon remove the implicit call to render_view_for, but here it is for your convenience.
272
+ def render_view_for_backward_compat(opts, state)
273
+ ::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"
274
+
275
+ render_view_for(opts, state)
276
+ end
277
+
278
+ # Renders the view for the current state and returns the markup for the component.
279
+ # Usually called and returned at the end of a state method.
280
+ #
281
+ # ==== Options
282
+ # * <tt>:view</tt> - Specifies the name of the view file to render. Defaults to the current state name.
283
+ # * <tt>:template_format</tt> - Allows using a format different to <tt>:html</tt>.
284
+ # * <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>.
285
+ # * <tt>:locals</tt> - Makes the named parameters available as variables in the view.
286
+ # * <tt>:text</tt> - Just renders plain text.
287
+ # * <tt>:inline</tt> - Renders an inline template as state view. See ActionView::Base#render for details.
288
+ # * <tt>:file</tt> - Specifies the name of the file template to render.
289
+ # * <tt>:nothing</tt> - Will make the component kinda invisible and doesn't invoke the rendering cycle.
290
+ # * <tt>:state</tt> - Instantly invokes another rendering cycle for the passed state and returns.
291
+ # Example:
292
+ # class MyCell < ::Cell::Base
293
+ # def my_first_state
294
+ # # ... do something
295
+ # render
296
+ # end
297
+ #
298
+ # will just render the view <tt>my_first_state.html</tt>.
299
+ #
300
+ # def my_first_state
301
+ # # ... do something
302
+ # render :view => :my_first_state, :layout => 'metal'
303
+ # end
304
+ #
305
+ # will also use the view <tt>my_first_state.html</tt> as template and even put it in the layout
306
+ # <tt>metal</tt> that's located at <tt>$RAILS_ROOT/app/cells/layouts/metal.html.erb</tt>.
307
+ #
308
+ # def say_your_name
309
+ # render :locals => {:name => "Nick"}
310
+ # end
311
+ #
312
+ # will make the variable +name+ available in the view <tt>say_your_name.html</tt>.
313
+ #
314
+ # def say_your_name
315
+ # render :nothing => true
316
+ # end
317
+ #
318
+ # will render an empty string thus keeping your name a secret.
319
+ #
320
+ #
321
+ # ==== Where have all the partials gone?
322
+ # In Cells we abandoned the term 'partial' in favor of plain 'views' - we don't need to distinguish
323
+ # between both terms. A cell view is both, a view and a kind of partial as it represents only a small
324
+ # part of the page.
325
+ # Just use <tt>:view</tt> and enjoy.
326
+ def render(opts={})
327
+ render_view_for(opts, @state_name) ### FIXME: i don't like the magic access to @state_name here. ugly!
328
+ end
329
+
330
+ # Render the view belonging to the given state. Will raise ActionView::MissingTemplate
331
+ # if it can not find one of the requested view template. Note that this behaviour was
332
+ # introduced in cells 2.3 and replaces the former warning message.
333
+ def render_view_for(opts, state)
334
+ return '' if opts[:nothing]
335
+
336
+ action_view = setup_action_view
337
+
338
+ ### TODO: dispatch dynamically:
339
+ if opts[:text]
340
+ elsif opts[:inline]
341
+ elsif opts[:file]
342
+ elsif opts[:state]
343
+ opts[:text] = render_state(opts[:state])
344
+ else
345
+ # handle :layout, :template_format, :view
346
+ opts = defaultize_render_options_for(opts, state)
347
+
348
+ # set instance vars, include helpers:
349
+ prepare_action_view_for(action_view, opts)
350
+
351
+ template = find_family_view_for_state_with_caching(opts[:view], action_view)
352
+ opts[:file] = template
353
+ end
354
+
355
+ opts = sanitize_render_options(opts)
356
+
357
+ action_view.render_for(opts)
358
+ end
359
+
360
+ # Defaultize the passed options from #render.
361
+ def defaultize_render_options_for(opts, state)
362
+ opts[:template_format] ||= self.class.default_template_format
363
+ opts[:view] ||= state
364
+ opts
365
+ end
366
+
367
+ def prepare_action_view_for(action_view, opts)
368
+ # make helpers available:
369
+ include_helpers_in_class(action_view.class)
370
+
371
+ action_view.assigns = assigns_for_view # make instance vars available.
372
+ action_view.template_format = opts[:template_format]
373
+ end
374
+
375
+ def setup_action_view
376
+ view_class = Class.new(::Cells::Cell::View)
377
+ action_view = view_class.new(self.class.view_paths, {}, @controller)
378
+ action_view.cell = self
379
+ action_view
380
+ end
381
+
382
+ # Prepares <tt>opts</tt> to be passed to ActionView::Base#render by removing
383
+ # unknown parameters.
384
+ def sanitize_render_options(opts)
385
+ opts.except!(:view, :state)
386
+ end
387
+
388
+ # Climbs up the inheritance hierarchy of the Cell, looking for a view
389
+ # for the current <tt>state</tt> in each level.
390
+ # As soon as a view file is found it is returned as an ActionView::Template
391
+ # instance.
392
+ ### DISCUSS: moved to Cell::View#find_template in rainhead's fork:
393
+ def find_family_view_for_state(state, action_view)
394
+ missing_template_exception = nil
395
+
396
+ possible_paths_for_state(state).each do |template_path|
397
+ # we need to catch MissingTemplate, since we want to try for all possible
398
+ # family views.
399
+ begin
400
+ if view = action_view.try_picking_template_for_path(template_path)
401
+ return view
402
+ end
403
+ rescue ::ActionView::MissingTemplate => missing_template_exception
404
+ end
405
+ end
406
+
407
+ raise missing_template_exception
408
+ end
409
+
410
+ # In production mode, the view for a state/template_format is cached.
411
+ ### DISCUSS: ActionView::Base already caches results for #pick_template, so maybe
412
+ ### we should just cache the family path for a state/format?
413
+ def find_family_view_for_state_with_caching(state, action_view)
414
+ return find_family_view_for_state(state, action_view) unless self.class.cache_configured?
415
+
416
+ # in production mode:
417
+ key = "#{state}/#{action_view.template_format}"
418
+ state2view = self.class.state2view_cache
419
+ state2view[key] || state2view[key] = find_family_view_for_state(state, action_view)
420
+ end
421
+
422
+ # Find possible files that belong to the state. This first tries the cell's
423
+ # <tt>#view_for_state</tt> method and if that returns a true value, it
424
+ # will accept that value as a string and interpret it as a pathname for
425
+ # the view file. If it returns a falsy value, it will call the Cell's class
426
+ # method find_class_view_for_state to determine the file to check.
427
+ #
428
+ # You can override the ::Cell::Base#view_for_state method for a particular
429
+ # cell if you wish to make it decide dynamically what file to render.
430
+ def possible_paths_for_state(state)
431
+ self.class.find_class_view_for_state(state).reverse!
432
+ end
433
+
434
+ # Prepares the hash {instance_var => value, ...} that should be available
435
+ # in the ActionView when rendering the state view.
436
+ def assigns_for_view
437
+ assigns = {}
438
+ (self.instance_variables - ivars_to_ignore).each do |k|
439
+ assigns[k[1..-1]] = instance_variable_get(k)
440
+ end
441
+ assigns
442
+ end
443
+
444
+ # When passed a copy of the ActionView::Base class, it
445
+ # will mix in all helper classes for this cell in that class.
446
+ def include_helpers_in_class(view_klass)
447
+ view_klass.send(:include, self.class.master_helper_module)
448
+ end
449
+
450
+ # Defines the instance variables that should <em>not</em> be copied to the
451
+ # View instance.
452
+ def ivars_to_ignore; ['@controller']; end
453
+
454
+ ### TODO: allow log levels.
455
+ def log(message)
456
+ return unless @controller.logger
457
+ @controller.logger.debug(message)
458
+ end
459
+ end
460
+ end
461
+ end