cells 2.3.0 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,163 @@
1
+ # encoding: utf-8
2
+
3
+ # To improve performance rendered state views can be cached using Rails' caching
4
+ # mechanism.
5
+ # If this it configured (e.g. using our fast friend memcached) all you have to do is to
6
+ # tell Cells which state you want to cache. You can further attach a proc to expire the
7
+ # cached view.
8
+ #
9
+ # As always I stole a lot of code, this time from Lance Ivy <cainlevy@gmail.com> and
10
+ # his fine components plugin at http://github.com/cainlevy/components.
11
+
12
+ module Cells
13
+ module Cell
14
+ module Caching
15
+
16
+ def self.included(base) #:nodoc:
17
+ base.class_eval do
18
+ # mixin ::Cell::Base#cache, setup vars and extend #render_state if caching's on.
19
+ extend ClassMethods
20
+
21
+ return unless cache_configured?
22
+
23
+ alias_method_chain :render_state, :caching
24
+ end
25
+ end
26
+
27
+ module ClassMethods
28
+ # Activate caching for the state <tt>state</tt>. If no other options are passed
29
+ # the view will be cached forever.
30
+ #
31
+ # You may pass a Proc or a Symbol as cache expiration <tt>version_proc</tt>.
32
+ # This method is called every time the state is rendered, and is expected to return a
33
+ # Hash containing the cache key ingredients.
34
+ #
35
+ # Additional options will be passed directly to the cache store when caching the state.
36
+ # Useful for simply setting a TTL for a cached state.
37
+ # Note that you may omit the <tt>version_proc</tt>.
38
+ #
39
+ #
40
+ # Example:
41
+ # class CachingCell < ::Cell::Base
42
+ # cache :versioned_cached_state, Proc.new{ {:version => 0} }
43
+ # would result in the complete cache key
44
+ # cells/CachingCell/versioned_cached_state/version=0
45
+ #
46
+ # If you provide a symbol, you can access the cell instance directly in the versioning
47
+ # method:
48
+ #
49
+ # class CachingCell < ::Cell::Base
50
+ # cache :cached_state, :my_cache_version
51
+ #
52
+ # def my_cache_version
53
+ # { :user => current_user.id,
54
+ # :item_id => params[:item] }
55
+ # }
56
+ # end
57
+ # results in a very specific cache key, for customized caching:
58
+ # cells/CachingCell/cached_state/user=18/item_id=1
59
+ #
60
+ # You may also set a TTL only, e.g. when using the memcached store:
61
+ #
62
+ # cache :cached_state, :expires_in => 3.minutes
63
+ #
64
+ # Or use both, having a versioning proc <em>and</em> a TTL expiring the state as a fallback
65
+ # after a certain amount of time.
66
+ #
67
+ # cache :cached_state, Proc.new { {:version => 0} }, :expires_in => 10.minutes
68
+ #--
69
+ ### TODO: implement for string, nil.
70
+ ### DISCUSS: introduce return method #sweep ? so the Proc can explicitly
71
+ ### delegate re-rendering to the outside.
72
+ #--
73
+ def cache(state, version_proc=nil, cache_opts={})
74
+ if version_proc.is_a?(Hash)
75
+ cache_opts = version_proc
76
+ version_proc = nil
77
+ end
78
+
79
+ version_procs[state] = version_proc
80
+ cache_options[state] = cache_opts
81
+ end
82
+
83
+ def version_procs
84
+ @version_procs ||= {}
85
+ end
86
+
87
+ def cache_options
88
+ @cache_options ||= {}
89
+ end
90
+
91
+ def cache_store #:nodoc:
92
+ ::ActionController::Base.cache_store
93
+ end
94
+
95
+ def cache_key_for(cell_class, state, args = {}) #:nodoc:
96
+ key_pieces = [cell_class, state]
97
+
98
+ args.collect{|a,b| [a.to_s, b]}.sort.each{ |k,v| key_pieces << "#{k}=#{v}" }
99
+ key = key_pieces.join('/')
100
+
101
+ ::ActiveSupport::Cache.expand_cache_key(key, :cells)
102
+ end
103
+
104
+ def expire_cache_key(key, opts=nil)
105
+ cache_store.delete(key, opts)
106
+ end
107
+ end
108
+
109
+ def render_state_with_caching(state)
110
+ return render_state_without_caching(state) unless state_cached?(state)
111
+
112
+ key = cache_key(state, call_version_proc_for_state(state))
113
+ ### DISCUSS: see sweep discussion at #cache.
114
+
115
+ # cache hit:
116
+ if content = read_fragment(key)
117
+ return content
118
+ end
119
+ # re-render:
120
+ return write_fragment(key, render_state_without_caching(state), cache_options[state])
121
+ end
122
+
123
+ def read_fragment(key, cache_options = nil) #:nodoc:
124
+ returning self.class.cache_store.read(key, cache_options) do |content|
125
+ log "Cell Cache hit: #{key}" unless content.blank?
126
+ end
127
+ end
128
+
129
+ def write_fragment(key, content, cache_opts = nil) #:nodoc:
130
+ log "Cell Cache miss: #{key}"
131
+ self.class.cache_store.write(key, content, cache_opts)
132
+ content
133
+ end
134
+
135
+ # Call the versioning Proc for the respective state.
136
+ def call_version_proc_for_state(state)
137
+ version_proc = version_procs[state]
138
+
139
+ return {} unless version_proc # call to #cache was without any args.
140
+
141
+ return version_proc.call(self) if version_proc.kind_of?(Proc)
142
+ send(version_proc)
143
+ end
144
+
145
+ def cache_key(state, args = {}) #:nodoc:
146
+ self.class.cache_key_for(self.cell_name, state, args)
147
+ end
148
+
149
+ def state_cached?(state)
150
+ self.class.version_procs.has_key?(state)
151
+ end
152
+
153
+ def version_procs
154
+ self.class.version_procs
155
+ end
156
+
157
+ def cache_options
158
+ self.class.cache_options
159
+ end
160
+
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,56 @@
1
+ # encoding: utf-8
2
+
3
+ module Cells
4
+ module Cell
5
+ class View < ::ActionView::Base
6
+
7
+ attr_accessor :cell
8
+ alias_method :render_for, :render
9
+
10
+ # Tries to find the passed template in view_paths. Returns the view on success-
11
+ # otherwise it will throw an ActionView::MissingTemplate exception.
12
+ def try_picking_template_for_path(template_path)
13
+ self.view_paths.find_template(template_path, template_format)
14
+ end
15
+
16
+ ### TODO: this should just be a thin helper.
17
+ ### dear rails folks, could you guys please provide a helper #render and an internal #render_for
18
+ ### so that we can overwrite the helper and cleanly reuse the internal method? using the same
19
+ ### method both internally and externally sucks ass.
20
+ def render(options = {}, local_assigns = {}, &block)
21
+ ### TODO: delegate dynamically:
22
+ ### TODO: we have to find out if this is a call to the cells #render method, or to the rails
23
+ ### method (e.g. when rendering a layout). what a shit.
24
+ if view = options[:view]
25
+ return cell.render_view_for(options, view)
26
+ end
27
+
28
+ # rails compatibility we should get rid of:
29
+ if partial_path = options[:partial]
30
+ # adds the cell name to the partial name.
31
+ options[:partial] = expand_view_path(partial_path)
32
+ end
33
+ #throw Exception.new
34
+
35
+ super(options, local_assigns, &block)
36
+ end
37
+
38
+ def expand_view_path(path)
39
+ path = "#{cell.cell_name}/#{path}" unless path.include?('/')
40
+ path
41
+ end
42
+
43
+ # this prevents cell ivars from being overwritten by same-named
44
+ # controller ivars.
45
+ # we'll hopefully get a cleaner way, or an API, to handle this in rails 3.
46
+ def _copy_ivars_from_controller #:nodoc:
47
+ if @controller
48
+ variables = @controller.instance_variable_names
49
+ variables -= @controller.protected_instance_variables if @controller.respond_to?(:protected_instance_variables)
50
+ variables -= assigns.keys.collect { |key| "@#{key}" } # cell ivars override controller ivars.
51
+ variables.each { |name| instance_variable_set(name, @controller.instance_variable_get(name)) }
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ module Cells
4
+ module Helpers
5
+ autoload :CaptureHelper, 'cells/helpers/capture_helper'
6
+ end
7
+ end
@@ -0,0 +1,51 @@
1
+ # encoding: utf-8
2
+
3
+ # Sorry for the interface violations, but it looks as if there are
4
+ # no interfaces in rails at all.
5
+ module Cells
6
+ module Helpers
7
+ module CaptureHelper
8
+ # Executes #capture on the global ActionView and sets <tt>name</tt> as the
9
+ # instance variable name.
10
+ #
11
+ # Example:
12
+ #
13
+ # <p>
14
+ # <% global_capture :greeting do
15
+ # <h1>Hi, Nick!</h1>
16
+ # <% end %>
17
+ #
18
+ # The captured markup can be accessed in your global action view or in your layout.
19
+ #
20
+ # <%= @greeting %>
21
+ def global_capture(name, &block)
22
+ global_view = controller.instance_variable_get(:@template)
23
+ content = capture(&block)
24
+ global_view.send(:instance_variable_set, :"@#{name}", content)
25
+ end
26
+
27
+
28
+ # Executes #content_for on the global ActionView.
29
+ #
30
+ # Example:
31
+ #
32
+ # <p>
33
+ # <% global_content_for :greetings do
34
+ # <h1>Hi, Michal!</h1>
35
+ # <% end %>
36
+ #
37
+ # As in global_capture, the markup can be accessed in your global action view or in your layout.
38
+ #
39
+ # <%= yield :greetings %>
40
+ def global_content_for(name, content = nil, &block)
41
+ # OMG.
42
+ global_view = controller.instance_variable_get(:@template)
43
+ ivar = :"@content_for_#{name}"
44
+ content = capture(&block) if block_given?
45
+ old_content = global_view.send(:'instance_variable_get', ivar)
46
+ global_view.send(:'instance_variable_set', ivar, "#{old_content}#{content}")
47
+ nil
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+ require 'cells/rails/action_controller'
3
+ require 'cells/rails/action_view'
4
+
5
+ Cell::Base.class_eval do
6
+ helper ::ApplicationHelper if defined?(::ApplicationHelper)
7
+ end
8
+
9
+ # Add extended ActionController behaviour.
10
+ ActionController::Base.class_eval do
11
+ include ::Cells::Rails::ActionController
12
+ end
13
+
14
+ # Add extended ActionView behaviour.
15
+ ActionView::Base.class_eval do
16
+ include ::Cells::Rails::ActionView
17
+ end
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+
3
+ # These ControllerMethods are automatically added to all Controllers when
4
+ # the cells plugin is loaded.
5
+ module Cells
6
+ module Rails
7
+ module ActionController
8
+ # Equivalent to ActionController#render_to_string, except it renders a cell
9
+ # rather than a regular templates.
10
+ def render_cell(name, state, opts={})
11
+ cell = ::Cell::Base.create_cell_for(self, name, opts)
12
+ return cell.render_state(state)
13
+ end
14
+ alias_method :render_cell_to_string, :render_cell # just for backward compatibility.
15
+
16
+ # Expires the cached cell state view, similar to ActionController::expire_fragment.
17
+ # Usually, this method is used in Sweepers.
18
+ # Beside the obvious first two args <tt>cell_name</tt> and <tt>state</tt> you can pass
19
+ # in additional cache key <tt>args</tt> and cache store specific <tt>opts</tt>.
20
+ #
21
+ # Example:
22
+ #
23
+ # class ListSweeper < ActionController::Caching::Sweeper
24
+ # observe List, Item
25
+ #
26
+ # def after_save(record)
27
+ # expire_cell_state :my_listing, :display_list
28
+ # end
29
+ #
30
+ # will expire the view for state <tt>:display_list</tt> in the cell <tt>MyListingCell</tt>.
31
+ def expire_cell_state(cell_name, state, args={}, opts=nil)
32
+ key = ::Cell::Base.cache_key_for(cell_name, state, args)
33
+ ::Cell::Base.expire_cache_key(key, opts)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+
3
+ # The Cells plugin defines a number of new methods for ActionView::Base. These allow
4
+ # you to render cells from within normal controller views as well as from Cell state views.
5
+ module Cells
6
+ module Rails
7
+ module ActionView
8
+ # Call a cell state and return its rendered view.
9
+ #
10
+ # ERB example:
11
+ # <div id="login">
12
+ # <%= render_cell :user, :login_prompt, :message => "Please login" %>
13
+ # </div>
14
+ #
15
+ # If you have a <tt>UserCell</tt> cell in <tt>app/cells/user_cell.rb</tt>, which has a
16
+ # <tt>UserCell#login_prompt</tt> method, this will call that method and then will
17
+ # find the view <tt>app/cells/user/login_prompt.html.erb</tt> and render it. This is
18
+ # called the <tt>:login_prompt</tt> <em>state</em> in Cells terminology.
19
+ #
20
+ # If this view file looks like this:
21
+ # <h1><%= @opts[:message] %></h1>
22
+ # <label>name: <input name="user[name]" /></label>
23
+ # <label>password: <input name="user[password]" /></label>
24
+ #
25
+ # The resulting view in the controller will be roughly equivalent to:
26
+ # <div id="login">
27
+ # <h1><%= "Please login" %></h1>
28
+ # <label>name: <input name="user[name]" /></label>
29
+ # <label>password: <input name="user[password]" /></label>
30
+ # </div>
31
+ def render_cell(name, state, opts = {})
32
+ cell = ::Cell::Base.create_cell_for(@controller, name, opts)
33
+ cell.render_state(state)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+
3
+ module Cells
4
+ VERSION = '3.3.0'.freeze
5
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+ require 'cells'
3
+
4
+ # Tell *Rails* to load files in path:
5
+ #
6
+ # * +app/cells+
7
+ #
8
+ ActiveSupport::Dependencies.load_paths << Rails.root.join(*%w[app cells])
9
+
10
+ # Rails initialization hook.
11
+ if defined?(Rails)
12
+ Rails.configuration.after_initialize do
13
+ initializer.loaded_plugins.each do |plugin|
14
+ engine_cells_dir = File.join(plugin.directory, *%w[app cells])
15
+
16
+ if plugin.engine? && File.exists?(engine_cells_dir)
17
+ # propagate the view- and code path of this engine-cell:
18
+ ::Cell::Base.view_paths << engine_cells_dir
19
+ ::ActiveSupport::Dependencies.load_paths << engine_cells_dir
20
+
21
+ # if a path is in +load_once_path+ it won't be reloaded between requests.
22
+ unless config.reload_plugins?
23
+ ::ActiveSupport::Dependencies.load_once_paths << engine_cells_dir
24
+ end
25
+ end
26
+ end
27
+ end
28
+ else
29
+ puts "[cells:] NOTE: Rails environment not available. Running isolated."
30
+ end
@@ -1,5 +1,6 @@
1
- class CellsTestOneCell < Cell::Base
1
+ # encoding: utf-8
2
2
 
3
+ class CellsTestOneCell < ::Cell::Base
3
4
  def super_state
4
5
  @my_class = self.class.to_s
5
6
  return
@@ -16,5 +17,4 @@ class CellsTestOneCell < Cell::Base
16
17
 
17
18
  def state_with_no_view
18
19
  end
19
-
20
20
  end
@@ -1,2 +1,4 @@
1
+ # encoding: utf-8
2
+
1
3
  class CellsTestTwoCell < CellsTestOneCell
2
4
  end
@@ -3,7 +3,7 @@ module NestedCell; end
3
3
 
4
4
  module ReallyModule
5
5
 
6
- class NestedCell < Cell::Base
6
+ class NestedCell < ::Cell::Base
7
7
  def happy_state
8
8
  end
9
9
  end