cells 3.3.10 → 3.4.0.beta1
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 +0 -8
- data/Gemfile +4 -1
- data/README.rdoc +91 -107
- data/Rakefile +0 -4
- data/lib/cell.rb +5 -6
- data/lib/{cells/cell → cell}/active_helper.rb +1 -1
- data/lib/cell/base.rb +134 -0
- data/lib/cell/base_methods.rb +100 -0
- data/lib/cell/caching.rb +153 -0
- data/lib/cell/rails.rb +239 -0
- data/lib/cells.rb +25 -3
- data/lib/cells/assertions_helper.rb +1 -1
- data/lib/cells/helpers/capture_helper.rb +3 -3
- data/lib/cells/rails.rb +65 -4
- data/lib/cells/version.rb +3 -1
- data/rails_generators/cell/cell_generator.rb +47 -35
- data/rails_generators/cell/templates/cell.rb +1 -1
- data/rails_generators/cells_install/cells_install_generator.rb +5 -3
- data/rails_generators/erb/cell_generator.rb +20 -0
- data/rails_generators/{cell → erb}/templates/view.html.erb +0 -0
- data/test/active_helper_test.rb +1 -0
- data/test/app/cells/bad_guitarist_cell.rb +2 -0
- data/test/app/cells/bassist_cell.rb +1 -1
- data/test/app/controllers/musician_controller.rb +16 -0
- data/test/assertions_helper_test.rb +8 -18
- data/test/base_methods_test.rb +40 -0
- data/test/cell_generator_test.rb +33 -21
- data/test/helper_test.rb +31 -123
- data/test/rails/caching_test.rb +215 -0
- data/test/rails/capture_test.rb +52 -0
- data/test/rails/cells_test.rb +88 -0
- data/test/rails/integration_test.rb +37 -0
- data/test/rails/render_test.rb +140 -0
- data/test/rails/router_test.rb +74 -0
- data/test/rails/view_test.rb +24 -0
- data/test/test_helper.rb +30 -29
- metadata +68 -133
- data/.gitignore +0 -3
- data/about.yml +0 -7
- data/cells.gemspec +0 -26
- data/lib/cells/cell.rb +0 -16
- data/lib/cells/cell/base.rb +0 -470
- data/lib/cells/cell/caching.rb +0 -163
- data/lib/cells/cell/test_case.rb +0 -158
- data/lib/cells/cell/view.rb +0 -55
- data/lib/cells/rails/action_controller.rb +0 -37
- data/lib/cells/rails/action_view.rb +0 -37
- data/rails/init.rb +0 -44
- data/rails_generators/cells_install/templates/tasks.rake +0 -6
- data/test/app/cells/a/another_state.html.erb +0 -1
- data/test/app/cells/a/existing_view.html.erb +0 -1
- data/test/app/cells/a/inherited_view.html.erb +0 -1
- data/test/app/cells/a/inherited_view.js.erb +0 -1
- data/test/app/cells/a/view_with_locals.html.erb +0 -1
- data/test/app/cells/a/view_with_render_call.html.erb +0 -1
- data/test/app/cells/b/existing_view.html.erb +0 -1
- data/test/app/cells/b/existing_view.js.erb +0 -1
- data/test/app/cells/b/layouts/metal.html.erb +0 -1
- data/test/app/cells/b/view_with_render_call.html.erb +0 -1
- data/test/app/cells/bassist/jam.html.erb +0 -3
- data/test/app/cells/bassist/play.html.erb +0 -1
- data/test/app/cells/cells_test_one/renamed_instance_view.html.erb +0 -1
- data/test/app/cells/cells_test_one/super_state.html.erb +0 -1
- data/test/app/cells/cells_test_one_cell.rb +0 -20
- data/test/app/cells/cells_test_two_cell.rb +0 -4
- data/test/app/cells/helper_using/state_using_application_helper.html.erb +0 -3
- data/test/app/cells/helper_using/state_with_automatic_helper_invocation.html.erb +0 -3
- data/test/app/cells/helper_using/state_with_helper_invocation.html.erb +0 -3
- data/test/app/cells/helper_using/state_with_helper_method_invocation.html.erb +0 -3
- data/test/app/cells/layouts/metal.html.erb +0 -1
- data/test/app/cells/my_child/hello.html.erb +0 -1
- data/test/app/cells/my_mother/bye.html.erb +0 -1
- data/test/app/cells/my_mother/hello.html.erb +0 -1
- data/test/app/cells/my_test/_broken_partial.html.erb +0 -1
- data/test/app/cells/my_test/_partial.html.erb +0 -1
- data/test/app/cells/my_test/state_with_instance_var.html.erb +0 -1
- data/test/app/cells/my_test/state_with_link_to.html.erb +0 -3
- data/test/app/cells/my_test/state_with_not_included_helper_method.html.erb +0 -8
- data/test/app/cells/my_test/view_containing_broken_partial.html.erb +0 -3
- data/test/app/cells/my_test/view_containing_nonexistant_partial.html.erb +0 -3
- data/test/app/cells/my_test/view_containing_partial.html.erb +0 -3
- data/test/app/cells/my_test/view_containing_partial_without_cell_name.html.erb +0 -3
- data/test/app/cells/my_test/view_in_local_test_views_dir.html.erb +0 -1
- data/test/app/cells/my_test/view_with_explicit_english_translation.en.html.erb +0 -1
- data/test/app/cells/my_test/view_with_explicit_english_translation.html.erb +0 -1
- data/test/app/cells/my_test/view_with_instance_var.html.erb +0 -4
- data/test/app/cells/really_module/nested/happy_state.html.erb +0 -1
- data/test/app/cells/really_module/nested_cell.rb +0 -11
- data/test/app/cells/simple/two_templates_state.html.mytpl +0 -1
- data/test/app/cells/simple_cell.rb +0 -7
- data/test/app/cells/test/beep.html.erb +0 -1
- data/test/app/cells/test/state_invoking_capture.html.erb +0 -7
- data/test/app/cells/test/state_invoking_content_for.html.erb +0 -7
- data/test/app/cells/test/state_invoking_content_for_twice.html.erb +0 -9
- data/test/app/cells/test/state_with_not_included_helper_method.html.erb +0 -8
- data/test/app/cells/two_helpers_including/state_using_another_helper.html.erb +0 -3
- data/test/bugs_test.rb +0 -23
- data/test/caching_test.rb +0 -270
- data/test/capture_helper_test.rb +0 -59
- data/test/cells_test.rb +0 -352
- data/test/rails_test.rb +0 -35
- data/test/render_test.rb +0 -305
- data/test/test_case_test.rb +0 -106
data/CHANGES
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
- 3.3.10
|
|
2
|
-
* TestCase#render_cell now processes options properly.
|
|
3
|
-
* added TestCase#assigns.
|
|
4
|
-
|
|
5
|
-
- 3.3.9
|
|
6
|
-
* fixed loading test_case.rb
|
|
7
|
-
* added TestCase#view_assigns
|
|
8
|
-
|
|
9
1
|
- 2.3
|
|
10
2
|
* ::Cell::Base#new(controller, opts={})
|
|
11
3
|
We got rid of the second argument cell_name, since it was completely useless.
|
data/Gemfile
CHANGED
data/README.rdoc
CHANGED
|
@@ -1,150 +1,134 @@
|
|
|
1
|
-
=
|
|
1
|
+
= Cells
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
However, their big advantage to controllers is their <em>modularity</em>: you can have
|
|
5
|
-
as many cells on a page as you want. That's as if you had multiple controllers in one
|
|
6
|
-
page, where each "controller" renders only a certain part of the page.
|
|
7
|
-
As if this wasn't enough, cells are superfast and lightweight.
|
|
3
|
+
<em>View Components for Rails.</em>
|
|
8
4
|
|
|
9
|
-
|
|
10
|
-
Michael.
|
|
5
|
+
== Overview
|
|
11
6
|
|
|
12
|
-
|
|
7
|
+
Say you're writing a Rails online shop - the shopping cart is reappearing again and again in every view. You're thinking about a clean solution for that part. A mixture of controller code, before-filters, partials and helpers?
|
|
13
8
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
script/generate cell Article newest top_article
|
|
9
|
+
No. That sucks. Take Cells.
|
|
17
10
|
|
|
11
|
+
Cells are View Components for Rails. They look and feel like controllers. They don't have no +DoubleRenderError+. They are callable everywhere in your controllers or views. They are cacheable, testable, fast and wonderful. They bring back OOP to your view and improve your software design.
|
|
18
12
|
|
|
19
|
-
|
|
20
|
-
this, after some editing:
|
|
13
|
+
== Installation
|
|
21
14
|
|
|
22
|
-
|
|
23
|
-
helper :my_formatting_and_escaping_helper # you can use helpers in cell views!
|
|
24
|
-
|
|
25
|
-
def newest
|
|
26
|
-
@articles = Article.get_newest
|
|
27
|
-
render # will render the view named newest.html.[erb|haml|...]".
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def top_article
|
|
31
|
-
@article = Article.top_article
|
|
32
|
-
render :view => :top_article_v2, # renders top_article_v2.html.[erb|haml|...]
|
|
33
|
-
:layout => :box # and put it in the layout "box.html".
|
|
34
|
-
end
|
|
35
|
-
end
|
|
15
|
+
It's a gem!
|
|
36
16
|
|
|
37
|
-
|
|
17
|
+
Rails 3:
|
|
38
18
|
|
|
39
|
-
|
|
40
|
-
<ul>
|
|
41
|
-
<% @articles.each do |article| %>
|
|
42
|
-
<li><%= article.title %></li>
|
|
43
|
-
<% end %>
|
|
44
|
-
</ul>
|
|
19
|
+
gem install cells
|
|
45
20
|
|
|
46
|
-
|
|
21
|
+
Rails 2.3:
|
|
47
22
|
|
|
48
|
-
|
|
49
|
-
= @article.title
|
|
50
|
-
= format_and_escape(@article.text)
|
|
23
|
+
gem install cells --version 3.3.4
|
|
51
24
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
25
|
+
|
|
26
|
+
== Generate
|
|
27
|
+
|
|
28
|
+
Creating a cell is nothing more than
|
|
29
|
+
|
|
30
|
+
$ rails generate cell ShoppingCart display
|
|
31
|
+
create app/cells/
|
|
32
|
+
create app/cells/shopping_cart
|
|
33
|
+
create app/cells/shopping_cart_cell.rb
|
|
34
|
+
create app/cells/shopping_cart/display.html.erb
|
|
35
|
+
create test/cells/shopping_cart_test.rb
|
|
36
|
+
|
|
37
|
+
That looks very familiar.
|
|
38
|
+
|
|
39
|
+
== Render the cell
|
|
40
|
+
|
|
41
|
+
Now, render your cart. Why not put it in <tt>layouts/application.html.erb</tt> for now?
|
|
42
|
+
|
|
43
|
+
<div id="header">
|
|
44
|
+
<%= render_cell :shopping_cart, :display, :user => @current_user %>
|
|
45
|
+
|
|
46
|
+
Feels like rendering a controller action. As additional encapsulation we pass the current +user+ from outside. Call it knowledge hiding.
|
|
47
|
+
|
|
48
|
+
== Code
|
|
49
|
+
|
|
50
|
+
Time to improve our cell code. Let's start with <tt>app/cells/shopping_cart_cell.rb</tt>:
|
|
51
|
+
|
|
52
|
+
class ShoppingCartCell < Cell::Rails
|
|
53
|
+
def display
|
|
54
|
+
@items = @opts[:user].items_in_cart
|
|
55
|
+
|
|
56
|
+
render # renders display.html.erb
|
|
59
57
|
end
|
|
60
58
|
end
|
|
61
59
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
<%= yield %>
|
|
65
|
-
|
|
66
|
-
<div><%= render_cell(:article, :newest) %></div>
|
|
67
|
-
<div><%= render_cell(:article, :top_article) %></div>
|
|
68
|
-
|
|
69
|
-
The "top page" would consist of the controller action's content, and two additional
|
|
70
|
-
independent boxes with interesting content. These two boxes are <em>cells</em> and could
|
|
71
|
-
be used on another page, too.
|
|
60
|
+
Is that a controller? Hell, yeah. We even got a +render+ method as we know it from the good ol' +ActionController+.
|
|
72
61
|
|
|
73
|
-
= Caching
|
|
74
62
|
|
|
75
|
-
|
|
76
|
-
If this it configured (e.g. using our fast friend memcached) all you have to do is to
|
|
77
|
-
tell Cells which state you want to cache. You can further attach a proc for deciding
|
|
78
|
-
versions or to instruct re-rendering.
|
|
63
|
+
== Views
|
|
79
64
|
|
|
80
|
-
|
|
65
|
+
Since a plain call to +render+ will start rendering <tt>app/cells/shopping_cart/display.html.erb</tt> we should put some meaningful markup there.
|
|
81
66
|
|
|
82
|
-
|
|
83
|
-
|
|
67
|
+
<div id="cart">
|
|
68
|
+
You have <%= @items.size %> items in your shopping cart.
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
=== Haml? Builder?
|
|
84
72
|
|
|
85
|
-
|
|
73
|
+
Yes, Cells support all template types that are supported by Rails itself. Remember- it's a controller!
|
|
86
74
|
|
|
87
|
-
|
|
75
|
+
=== Helpers
|
|
88
76
|
|
|
89
|
-
|
|
77
|
+
Yes, Cells have helpers just like controllers. If you need some specific helper, do
|
|
90
78
|
|
|
91
|
-
|
|
79
|
+
class ShoppingCartCell < Cell::Rails
|
|
80
|
+
helper MyExtraHelper
|
|
92
81
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
from the i18n helper can also be used in cell views.
|
|
82
|
+
and it will be around in your cart views.
|
|
96
83
|
|
|
97
|
-
=== Haml
|
|
98
84
|
|
|
99
|
-
|
|
100
|
-
of your choice (.erb, .haml, ...) to write your cell views.
|
|
85
|
+
== Caching
|
|
101
86
|
|
|
102
|
-
|
|
87
|
+
Cells do strict view caching. No cluttered fragment caching. Add
|
|
103
88
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
and can be used everywhere in your application.
|
|
89
|
+
class ShoppingCartCell < Cell::Rails
|
|
90
|
+
cache :display, :expires_in => 10.minutes
|
|
107
91
|
|
|
108
|
-
|
|
92
|
+
and your cart will be re-rendered after 10 minutes.
|
|
109
93
|
|
|
110
|
-
|
|
94
|
+
There are multiple advanced options for expiring your view caches, including an expiration lambda.
|
|
95
|
+
|
|
96
|
+
class ShoppingCartCell < Cell::Rails
|
|
97
|
+
cache :display do |cell|
|
|
98
|
+
Item.still_valid?
|
|
99
|
+
end
|
|
111
100
|
|
|
112
|
-
script/plugin install git://github.com/apotonick/cells.git
|
|
113
|
-
|
|
114
|
-
This release is tested and runs with Rails 2.3.
|
|
115
101
|
|
|
102
|
+
== Testing
|
|
116
103
|
|
|
117
|
-
|
|
104
|
+
Another big advantage compared to monolithic controller/helper/partial piles is the ability to test your cells isolated.
|
|
118
105
|
|
|
119
|
-
|
|
106
|
+
So what if you wanna test the cart cell? Use the generated <tt>test/cells/shopping_cart_test.rb</tt> test.
|
|
120
107
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
108
|
+
class ShoppingCartTest < ActionController::TestCase
|
|
109
|
+
include Cells::AssertionsHelper
|
|
110
|
+
|
|
111
|
+
test "display" do
|
|
112
|
+
html = render_cell(:shopping_cart, :diplay, :user => @user_fixture)
|
|
113
|
+
assert_selekt html, "#cart", "You have 3 items in your shopping cart."
|
|
124
114
|
|
|
125
|
-
|
|
115
|
+
That's easy, clean and strongly improves your component-driven software quality. How'd you do that with partials?
|
|
126
116
|
|
|
127
|
-
Copyright (c) 2007-2009, Nick Sutterer
|
|
128
117
|
|
|
129
|
-
|
|
118
|
+
== More features
|
|
130
119
|
|
|
131
|
-
|
|
120
|
+
Cells can do more.
|
|
121
|
+
|
|
122
|
+
<b>View Inheritance</b>:: Inherit view files dynamically from parent cells.
|
|
123
|
+
<b>Cell Nesting</b>:: Have complex cell hierarchies as you can call +render_cell+ within cells, too.
|
|
124
|
+
|
|
125
|
+
Go for it, you'll love it!
|
|
132
126
|
|
|
133
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
134
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
135
|
-
in the Software without restriction, including without limitation the rights
|
|
136
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
137
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
138
|
-
furnished to do so, subject to the following conditions:
|
|
139
127
|
|
|
140
|
-
|
|
141
|
-
all copies or substantial portions of the Software.
|
|
128
|
+
== LICENSE
|
|
142
129
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
147
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
148
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
149
|
-
THE SOFTWARE.
|
|
130
|
+
Copyright (c) 2007-2010, Nick Sutterer
|
|
131
|
+
|
|
132
|
+
Copyright (c) 2007-2008, Solide ICT by Peter Bex and Bob Leers
|
|
150
133
|
|
|
134
|
+
Released under the MIT License.
|
data/Rakefile
CHANGED
data/lib/cell.rb
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
# encoding: utf-8
|
|
2
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
3
|
module Cell
|
|
7
|
-
Base
|
|
8
|
-
View
|
|
4
|
+
#autoload :Base, 'cell/base'
|
|
5
|
+
autoload :View, 'cell/view'
|
|
6
|
+
autoload :Caching, 'cell/caching'
|
|
7
|
+
autoload :ActiveHelper, 'cell/active_helper'
|
|
9
8
|
end
|
data/lib/cell/base.rb
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# == Basic overview
|
|
2
|
+
#
|
|
3
|
+
# A Cell is the central notion of the cells plugin. A cell acts as a
|
|
4
|
+
# lightweight controller in the sense that it will assign variables and
|
|
5
|
+
# render a view. Cells can be rendered from other cells as well as from
|
|
6
|
+
# regular controllers and views (see ActionView::Base#render_cell and
|
|
7
|
+
# ControllerMethods#render_cell)
|
|
8
|
+
#
|
|
9
|
+
# == A render_cell() cycle
|
|
10
|
+
#
|
|
11
|
+
# A typical <tt>render_cell</tt> state rendering cycle looks like this:
|
|
12
|
+
# render_cell :blog, :newest_article, {...}
|
|
13
|
+
# - an instance of the class <tt>BlogCell</tt> is created, and a hash containing
|
|
14
|
+
# arbitrary parameters is passed
|
|
15
|
+
# - the <em>state method</em> <tt>newest_article</tt> is executed and assigns instance
|
|
16
|
+
# variables to be used in the view
|
|
17
|
+
# - Usually the state method will call #render and return
|
|
18
|
+
# - #render will retrieve the corresponding view
|
|
19
|
+
# (e.g. <tt>app/cells/blog/newest_article.html. [erb|haml|...]</tt>),
|
|
20
|
+
# renders this template and returns the markup.
|
|
21
|
+
#
|
|
22
|
+
# == Design Principles
|
|
23
|
+
# A cell is a completely autonomous object and it should not know or have to know
|
|
24
|
+
# from what controller it is being rendered. For this reason, the controller's
|
|
25
|
+
# instance variables and params hash are not directly available from the cell or
|
|
26
|
+
# its views. This is not a bug, this is a feature! It means cells are truly
|
|
27
|
+
# reusable components which can be plugged in at any point in your application
|
|
28
|
+
# without having to think about what information is available at that point.
|
|
29
|
+
# When rendering a cell, you can explicitly pass variables to the cell in the
|
|
30
|
+
# extra opts argument hash, just like you would pass locals in partials.
|
|
31
|
+
# This hash is then available inside the cell as the @opts instance variable.
|
|
32
|
+
#
|
|
33
|
+
# == Directory hierarchy
|
|
34
|
+
#
|
|
35
|
+
# To get started creating your own cells, you can simply create a new directory
|
|
36
|
+
# structure under your <tt>app</tt> directory called <tt>cells</tt>. Cells are
|
|
37
|
+
# ruby classes which end in the name Cell. So for example, if you have a
|
|
38
|
+
# cell which manages all user information, it would be called <tt>UserCell</tt>.
|
|
39
|
+
# A cell which manages a shopping cart could be called <tt>ShoppingCartCell</tt>.
|
|
40
|
+
#
|
|
41
|
+
# The directory structure of this example would look like this:
|
|
42
|
+
# app/
|
|
43
|
+
# models/
|
|
44
|
+
# ..
|
|
45
|
+
# views/
|
|
46
|
+
# ..
|
|
47
|
+
# helpers/
|
|
48
|
+
# application_helper.rb
|
|
49
|
+
# product_helper.rb
|
|
50
|
+
# ..
|
|
51
|
+
# controllers/
|
|
52
|
+
# ..
|
|
53
|
+
# cells/
|
|
54
|
+
# shopping_cart_cell.rb
|
|
55
|
+
# shopping_cart/
|
|
56
|
+
# status.html.erb
|
|
57
|
+
# product_list.html.erb
|
|
58
|
+
# empty_prompt.html.erb
|
|
59
|
+
# user_cell.rb
|
|
60
|
+
# user/
|
|
61
|
+
# login.html.erb
|
|
62
|
+
# layouts/
|
|
63
|
+
# box.html.erb
|
|
64
|
+
# ..
|
|
65
|
+
#
|
|
66
|
+
# The directory with the same name as the cell contains views for the
|
|
67
|
+
# cell's <em>states</em>. A state is an executed method along with a
|
|
68
|
+
# rendered view, resulting in content. This means that states are to
|
|
69
|
+
# cells as actions are to controllers, so each state has its own view.
|
|
70
|
+
# The use of partials is deprecated with cells, it is better to just
|
|
71
|
+
# render a different state on the same cell (which also works recursively).
|
|
72
|
+
#
|
|
73
|
+
# Anyway, <tt>render :partial </tt> in a cell view will work, if the
|
|
74
|
+
# partial is contained in the cell's view directory.
|
|
75
|
+
#
|
|
76
|
+
# As can be seen above, Cells also can make use of helpers. All Cells
|
|
77
|
+
# include ApplicationHelper by default, but you can add additional helpers
|
|
78
|
+
# as well with the ::Cell::Base.helper class method:
|
|
79
|
+
# class ShoppingCartCell < ::Cell::Base
|
|
80
|
+
# helper :product
|
|
81
|
+
# ...
|
|
82
|
+
# end
|
|
83
|
+
#
|
|
84
|
+
# This will make the <tt>ProductHelper</tt> from <tt>app/helpers/product_helper.rb</tt>
|
|
85
|
+
# available from all state views from our <tt>ShoppingCartCell</tt>.
|
|
86
|
+
#
|
|
87
|
+
# == Cell inheritance
|
|
88
|
+
#
|
|
89
|
+
# Unlike controllers, Cells can form a class hierarchy. When a cell class
|
|
90
|
+
# is inherited by another cell class, its states are inherited as regular
|
|
91
|
+
# methods are, but also its views are inherited. Whenever a view is looked up,
|
|
92
|
+
# the view finder first looks for a file in the directory belonging to the
|
|
93
|
+
# current cell class, but if this is not found in the application or any
|
|
94
|
+
# engine, the superclass' directory is checked. This continues all the
|
|
95
|
+
# way up until it stops at ::Cell::Base.
|
|
96
|
+
#
|
|
97
|
+
# For instance, when you have two cells:
|
|
98
|
+
# class MenuCell < ::Cell::Base
|
|
99
|
+
# def show
|
|
100
|
+
# end
|
|
101
|
+
#
|
|
102
|
+
# def edit
|
|
103
|
+
# end
|
|
104
|
+
# end
|
|
105
|
+
#
|
|
106
|
+
# class MainMenuCell < MenuCell
|
|
107
|
+
# .. # no need to redefine show/edit if they do the same!
|
|
108
|
+
# end
|
|
109
|
+
# and the following directory structure in <tt>app/cells</tt>:
|
|
110
|
+
# app/cells/
|
|
111
|
+
# menu/
|
|
112
|
+
# show.html.erb
|
|
113
|
+
# edit.html.erb
|
|
114
|
+
# main_menu/
|
|
115
|
+
# show.html.erb
|
|
116
|
+
# then when you call
|
|
117
|
+
# render_cell :main_menu, :show
|
|
118
|
+
# the main menu specific show.html.erb (<tt>app/cells/main_menu/show.html.erb</tt>)
|
|
119
|
+
# is rendered, but when you call
|
|
120
|
+
# render_cell :main_menu, :edit
|
|
121
|
+
# cells notices that the main menu does not have a specific view for the
|
|
122
|
+
# <tt>edit</tt> state, so it will render the view for the parent class,
|
|
123
|
+
# <tt>app/cells/menu/edit.html.erb</tt>
|
|
124
|
+
#
|
|
125
|
+
#
|
|
126
|
+
# == Gettext support
|
|
127
|
+
#
|
|
128
|
+
# Cells support gettext, just name your views accordingly. It works exactly equivalent
|
|
129
|
+
# to controller views.
|
|
130
|
+
#
|
|
131
|
+
# cells/user/user_form.html.erb
|
|
132
|
+
# cells/user/user_form_de.html.erb
|
|
133
|
+
#
|
|
134
|
+
# If gettext is set to DE_de, the latter view will be chosen.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
module Cell
|
|
2
|
+
module BaseMethods
|
|
3
|
+
def self.included(base)
|
|
4
|
+
base.extend ClassMethods
|
|
5
|
+
|
|
6
|
+
### DISCUSS: move that to Rails?
|
|
7
|
+
base.class_attribute :default_template_format
|
|
8
|
+
base.default_template_format = :html
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module ClassMethods
|
|
12
|
+
def render_cell_for(controller, name, state, opts={})
|
|
13
|
+
create_cell_for(controller, name, opts).render_state(state) # FIXME: don't let BaseMethods know about controller's API.
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Creates a cell instance of the class <tt>name</tt>Cell, passing through
|
|
17
|
+
# <tt>opts</tt>.
|
|
18
|
+
def create_cell_for(controller, name, opts={})
|
|
19
|
+
#class_from_cell_name(name).new(controller, opts)
|
|
20
|
+
class_from_cell_name(name).new(controller, opts)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Return the default view for the given state on this cell subclass.
|
|
24
|
+
# This is a file with the name of the state under a directory with the
|
|
25
|
+
# name of the cell followed by a template extension.
|
|
26
|
+
def view_for_state(state)
|
|
27
|
+
"#{cell_name}/#{state}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Find a possible template for a cell's current state. It tries to find a
|
|
31
|
+
# template file with the name of the state under a subdirectory
|
|
32
|
+
# with the name of the cell under the <tt>app/cells</tt> directory.
|
|
33
|
+
# If this file cannot be found, it will try to call this method on
|
|
34
|
+
# the superclass. This way you only have to write a state template
|
|
35
|
+
# once when a more specific cell does not need to change anything in
|
|
36
|
+
# that view.
|
|
37
|
+
def find_class_view_for_state(state)
|
|
38
|
+
return [view_for_state(state)] unless superclass.respond_to?(:find_class_view_for_state)
|
|
39
|
+
|
|
40
|
+
superclass.find_class_view_for_state(state) << view_for_state(state)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Get the name of this cell's class as an underscored string,
|
|
44
|
+
# with _cell removed.
|
|
45
|
+
#
|
|
46
|
+
# Example:
|
|
47
|
+
# UserCell.cell_name
|
|
48
|
+
# => "user"
|
|
49
|
+
def cell_name
|
|
50
|
+
name.underscore.sub(/_cell$/, '')
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def class_from_cell_name(cell_name)
|
|
54
|
+
"#{cell_name}_cell".classify.constantize
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
attr_accessor :controller
|
|
63
|
+
attr_reader :state_name
|
|
64
|
+
|
|
65
|
+
def initialize(options={})
|
|
66
|
+
#@controller = controller
|
|
67
|
+
@opts = options
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def cell_name
|
|
71
|
+
self.class.cell_name
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Invoke the state method and render the given state.
|
|
75
|
+
def render_state(state, controller=nil)
|
|
76
|
+
@cell = self
|
|
77
|
+
@state_name = state
|
|
78
|
+
|
|
79
|
+
dispatch_state(state)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Call the state method.
|
|
83
|
+
def dispatch_state(state)
|
|
84
|
+
send(state)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Find possible files that belong to the state. This first tries the cell's
|
|
88
|
+
# <tt>#view_for_state</tt> method and if that returns a true value, it
|
|
89
|
+
# will accept that value as a string and interpret it as a pathname for
|
|
90
|
+
# the view file. If it returns a falsy value, it will call the Cell's class
|
|
91
|
+
# method find_class_view_for_state to determine the file to check.
|
|
92
|
+
#
|
|
93
|
+
# You can override the ::Cell::Base#view_for_state method for a particular
|
|
94
|
+
# cell if you wish to make it decide dynamically what file to render.
|
|
95
|
+
def possible_paths_for_state(state)
|
|
96
|
+
self.class.find_class_view_for_state(state).reverse!
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
end
|
|
100
|
+
end
|