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.
- data/CHANGES +3 -3
- data/MIT-LICENSE +22 -0
- data/README.rdoc +2 -2
- data/Rakefile +22 -25
- data/generators/cell/templates/cell.rb +1 -1
- data/generators/cells_install/USAGE +3 -0
- data/generators/cells_install/cells_install_generator.rb +12 -0
- data/generators/cells_install/templates/initializer.rb +9 -0
- data/lib/cell.rb +9 -0
- data/lib/cells.rb +68 -0
- data/lib/cells/cell.rb +15 -0
- data/lib/cells/cell/base.rb +461 -0
- data/lib/cells/cell/caching.rb +163 -0
- data/lib/cells/cell/view.rb +56 -0
- data/lib/cells/helpers.rb +7 -0
- data/lib/cells/helpers/capture_helper.rb +51 -0
- data/lib/cells/rails.rb +17 -0
- data/lib/cells/rails/action_controller.rb +37 -0
- data/lib/cells/rails/action_view.rb +37 -0
- data/lib/cells/version.rb +5 -0
- data/rails/init.rb +30 -0
- data/test/{cells → app/cells}/cells_test_one_cell.rb +2 -2
- data/test/{cells → app/cells}/cells_test_two_cell.rb +2 -0
- data/test/{cells → app/cells}/really_module/nested_cell.rb +1 -1
- data/test/app/cells/simple_cell.rb +7 -0
- data/test/{cells → app/cells}/test_cell.rb +3 -7
- data/test/app/controllers/cells_test_controller.rb +44 -0
- data/test/app/helpers/application_helper.rb +7 -0
- data/test/{helpers → app/helpers}/helper_using_cell_helper.rb +3 -1
- data/test/bugs_test.rb +10 -13
- data/test/caching_test.rb +169 -165
- data/test/capture_helper_test.rb +59 -0
- data/test/cells_test.rb +160 -158
- data/test/helper_test.rb +83 -104
- data/test/rails_test.rb +35 -0
- data/test/render_test.rb +163 -106
- data/test/support/assertions_helper.rb +60 -0
- data/test/test_helper.rb +67 -0
- metadata +35 -25
- data/README +0 -150
- data/VERSION +0 -1
- data/init.rb +0 -59
- data/lib/cell/base.rb +0 -454
- data/lib/cell/caching.rb +0 -151
- data/lib/cell/view.rb +0 -55
- data/lib/cells_helper.rb +0 -49
- data/lib/rails_extensions.rb +0 -75
- data/test/capture_test.rb +0 -56
- data/test/cell_view_test.rb +0 -9
- data/test/cells/simple_cell.rb +0 -5
- data/test/rails_extensions_test.rb +0 -25
- 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 |
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
#
|
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
|
-
|
73
|
+
"Install it with: sudo gem install jeweler -s http://gemcutter.org"
|
77
74
|
end
|
@@ -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
|
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
|