reactive 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/History.txt +3 -0
  2. data/MIT-LICENSE +21 -0
  3. data/Manifest.txt +60 -0
  4. data/README.txt +130 -0
  5. data/Rakefile +14 -0
  6. data/app_generators/reactive/USAGE +11 -0
  7. data/app_generators/reactive/reactive_generator.rb +160 -0
  8. data/app_generators/reactive/templates/README +130 -0
  9. data/app_generators/reactive/templates/Rakefile +10 -0
  10. data/app_generators/reactive/templates/app/controllers/application_controller.rb +2 -0
  11. data/app_generators/reactive/templates/app/helpers/application_helper.rb +2 -0
  12. data/app_generators/reactive/templates/config/boot.rb +94 -0
  13. data/app_generators/reactive/templates/config/databases/frontbase.yml +28 -0
  14. data/app_generators/reactive/templates/config/databases/mysql.yml +54 -0
  15. data/app_generators/reactive/templates/config/databases/oracle.yml +39 -0
  16. data/app_generators/reactive/templates/config/databases/postgresql.yml +48 -0
  17. data/app_generators/reactive/templates/config/databases/sqlite2.yml +16 -0
  18. data/app_generators/reactive/templates/config/databases/sqlite3.yml +19 -0
  19. data/app_generators/reactive/templates/config/empty.log +0 -0
  20. data/app_generators/reactive/templates/config/environment.rb +11 -0
  21. data/app_generators/reactive/templates/script/destroy +12 -0
  22. data/app_generators/reactive/templates/script/generate +12 -0
  23. data/app_generators/reactive/templates/script/run +5 -0
  24. data/app_generators/reactive/templates/script/win_script.cmd +1 -0
  25. data/bin/reactive +16 -0
  26. data/lib/code_statistics.rb +107 -0
  27. data/lib/controller.rb +23 -0
  28. data/lib/controller/base.rb +442 -0
  29. data/lib/controller/filters.rb +767 -0
  30. data/lib/controller/flash.rb +161 -0
  31. data/lib/controller/helpers.rb +204 -0
  32. data/lib/controller/layout.rb +326 -0
  33. data/lib/dispatcher.rb +46 -0
  34. data/lib/generated_attribute.rb +40 -0
  35. data/lib/initializer.rb +425 -0
  36. data/lib/named_base_generator.rb +92 -0
  37. data/lib/reactive.rb +6 -0
  38. data/lib/request.rb +17 -0
  39. data/lib/source_annotation_extractor.rb +62 -0
  40. data/lib/tasks/annotations.rake +23 -0
  41. data/lib/tasks/databases.rake +347 -0
  42. data/lib/tasks/log.rake +9 -0
  43. data/lib/tasks/misc.rake +5 -0
  44. data/lib/tasks/reactive.rb +16 -0
  45. data/lib/tasks/statistics.rake +17 -0
  46. data/lib/tasks/testing.rake +118 -0
  47. data/lib/version.rb +9 -0
  48. data/lib/view.rb +1 -0
  49. data/lib/view/base.rb +33 -0
  50. data/reactive_generators/model/USAGE +27 -0
  51. data/reactive_generators/model/model_generator.rb +52 -0
  52. data/reactive_generators/model/templates/fixtures.yml +19 -0
  53. data/reactive_generators/model/templates/migration.rb +16 -0
  54. data/reactive_generators/model/templates/model.rb +2 -0
  55. data/reactive_generators/model/templates/unit_test.rb +8 -0
  56. data/reactive_generators/scaffold/USAGE +26 -0
  57. data/reactive_generators/scaffold/scaffold_generator.rb +75 -0
  58. data/reactive_generators/scaffold/templates/controller.rb +51 -0
  59. data/reactive_generators/scaffold/templates/functional_test.rb +49 -0
  60. data/reactive_generators/scaffold/templates/helper.rb +2 -0
  61. metadata +142 -0
@@ -0,0 +1,161 @@
1
+ module Reactive::Controller #:nodoc:
2
+ # The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed
3
+ # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
4
+ # action that sets <tt>flash[:notice] = "Successfully created"</tt> before redirecting to a display action that can
5
+ # then expose the flash to its template. Actually, that exposure is automatically done. Example:
6
+ #
7
+ # class WeblogController < Reactive::Controller::Base
8
+ # def create
9
+ # # save post
10
+ # flash[:notice] = "Successfully created post"
11
+ # redirect_to :action => "display", :params => { :id => post.id }
12
+ # end
13
+ #
14
+ # def display
15
+ # # doesn't need to assign the flash notice to the template, that's done automatically
16
+ # end
17
+ # end
18
+ #
19
+ # display.erb
20
+ # <% if flash[:notice] %><div class="notice"><%= flash[:notice] %></div><% end %>
21
+ #
22
+ # This example just places a string in the flash, but you can put any object in there. And of course, you can put as
23
+ # many as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
24
+ #
25
+ # See docs on the FlashHash class for more details about the flash.
26
+ module Flash
27
+ def self.included(base)
28
+ base.class_eval do
29
+ include InstanceMethods
30
+ alias_method_chain :assign_shortcuts, :flash
31
+ alias_method_chain :process_cleanup, :flash
32
+ end
33
+ end
34
+
35
+
36
+ class FlashNow #:nodoc:
37
+ def initialize(flash)
38
+ @flash = flash
39
+ end
40
+
41
+ def []=(k, v)
42
+ @flash[k] = v
43
+ @flash.discard(k)
44
+ v
45
+ end
46
+
47
+ def [](k)
48
+ @flash[k]
49
+ end
50
+ end
51
+
52
+ class FlashHash < Hash
53
+ def initialize #:nodoc:
54
+ super
55
+ @used = {}
56
+ end
57
+
58
+ def []=(k, v) #:nodoc:
59
+ keep(k)
60
+ super
61
+ end
62
+
63
+ def update(h) #:nodoc:
64
+ h.keys.each { |k| keep(k) }
65
+ super
66
+ end
67
+
68
+ alias :merge! :update
69
+
70
+ def replace(h) #:nodoc:
71
+ @used = {}
72
+ super
73
+ end
74
+
75
+ # Sets a flash that will not be available to the next action, only to the current.
76
+ #
77
+ # flash.now[:message] = "Hello current action"
78
+ #
79
+ # This method enables you to use the flash as a central messaging system in your app.
80
+ # When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>).
81
+ # When you need to pass an object to the current action, you use <tt>now</tt>, and your object will
82
+ # vanish when the current action is done.
83
+ #
84
+ # Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
85
+ def now
86
+ FlashNow.new(self)
87
+ end
88
+
89
+ # Keeps either the entire current flash or a specific flash entry available for the next action:
90
+ #
91
+ # flash.keep # keeps the entire flash
92
+ # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
93
+ def keep(k = nil)
94
+ use(k, false)
95
+ end
96
+
97
+ # Marks the entire flash or a single flash entry to be discarded by the end of the current action:
98
+ #
99
+ # flash.discard # discard the entire flash at the end of the current action
100
+ # flash.discard(:warning) # discard only the "warning" entry at the end of the current action
101
+ def discard(k = nil)
102
+ use(k)
103
+ end
104
+
105
+ # Mark for removal entries that were kept, and delete unkept ones.
106
+ #
107
+ # This method is called automatically by filters, so you generally don't need to care about it.
108
+ def sweep #:nodoc:
109
+ keys.each do |k|
110
+ unless @used[k]
111
+ use(k)
112
+ else
113
+ delete(k)
114
+ @used.delete(k)
115
+ end
116
+ end
117
+
118
+ # clean up after keys that could have been left over by calling reject! or shift on the flash
119
+ (@used.keys - keys).each{ |k| @used.delete(k) }
120
+ end
121
+
122
+ private
123
+ # Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
124
+ # use() # marks the entire flash as used
125
+ # use('msg') # marks the "msg" entry as used
126
+ # use(nil, false) # marks the entire flash as unused (keeps it around for one more action)
127
+ # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
128
+ def use(k=nil, v=true)
129
+ unless k.nil?
130
+ @used[k] = v
131
+ else
132
+ keys.each{ |key| use(key, v) }
133
+ end
134
+ end
135
+ end
136
+
137
+ module InstanceMethods #:nodoc:
138
+ protected
139
+ # Access the contents of the flash. Use <tt>flash["notice"]</tt> to read a notice you put there or
140
+ # <tt>flash["notice"] = "hello"</tt> to put a new one.
141
+ # Note that if sessions are disabled only flash.now will work.
142
+ def flash(refresh = false) #:doc:
143
+ if !defined?(@_flash) || refresh
144
+ @_flash = FlashHash.new
145
+ end
146
+ @_flash
147
+ end
148
+
149
+ private
150
+ def assign_shortcuts_with_flash(request, response) #:nodoc:
151
+ assign_shortcuts_without_flash(request, response)
152
+ flash(:refresh)
153
+ end
154
+
155
+ def process_cleanup_with_flash
156
+ flash.sweep if @_session
157
+ process_cleanup_without_flash
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,204 @@
1
+ # FIXME: helper { ... } is broken on Ruby 1.9
2
+ module Reactive::Controller #:nodoc:
3
+ module Helpers #:nodoc:
4
+ HELPERS_DIR = (defined?(REACTIVE_ROOT) ? "#{REACTIVE_ROOT}/app/helpers" : "app/helpers")
5
+
6
+ def self.included(base)
7
+ # Initialize the base module to aggregate its helpers.
8
+ base.class_inheritable_accessor :master_helper_module
9
+ base.master_helper_module = Module.new
10
+
11
+ # Extend base with class methods to declare helpers.
12
+ base.extend(ClassMethods)
13
+
14
+ base.class_eval do
15
+ # Wrap inherited to create a new master helper module for subclasses.
16
+ class << self
17
+ alias_method_chain :inherited, :helper
18
+ end
19
+ end
20
+ end
21
+
22
+ # The Rails framework provides a large number of helpers for working with +assets+, +dates+, +forms+,
23
+ # +numbers+ and +ActiveRecord+ objects, to name a few. These helpers are available to all templates
24
+ # by default.
25
+ #
26
+ # In addition to using the standard template helpers provided in the Rails framework, creating custom helpers to
27
+ # extract complicated logic or reusable functionality is strongly encouraged. By default, the controller will
28
+ # include a helper whose name matches that of the controller, e.g., <tt>MyController</tt> will automatically
29
+ # include <tt>MyHelper</tt>.
30
+ #
31
+ # Additional helpers can be specified using the +helper+ class method in <tt>ActionController::Base</tt> or any
32
+ # controller which inherits from it.
33
+ #
34
+ # ==== Examples
35
+ # The +to_s+ method from the +Time+ class can be wrapped in a helper method to display a custom message if
36
+ # the Time object is blank:
37
+ #
38
+ # module FormattedTimeHelper
39
+ # def format_time(time, format=:long, blank_message="&nbsp;")
40
+ # time.blank? ? blank_message : time.to_s(format)
41
+ # end
42
+ # end
43
+ #
44
+ # +FormattedTimeHelper+ can now be included in a controller, using the +helper+ class method:
45
+ #
46
+ # class EventsController < ActionController::Base
47
+ # helper FormattedTimeHelper
48
+ # def index
49
+ # @events = Event.find(:all)
50
+ # end
51
+ # end
52
+ #
53
+ # Then, in any view rendered by <tt>EventController</tt>, the <tt>format_time</tt> method can be called:
54
+ #
55
+ # <% @events.each do |event| -%>
56
+ # <p>
57
+ # <% format_time(event.time, :short, "N/A") %> | <%= event.name %>
58
+ # </p>
59
+ # <% end -%>
60
+ #
61
+ # Finally, assuming we have two event instances, one which has a time and one which does not,
62
+ # the output might look like this:
63
+ #
64
+ # 23 Aug 11:30 | Carolina Railhawks Soccer Match
65
+ # N/A | Carolina Railhaws Training Workshop
66
+ #
67
+ module ClassMethods
68
+ # Makes all the (instance) methods in the helper module available to templates rendered through this controller.
69
+ # See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules
70
+ # available to the templates.
71
+ def add_template_helper(helper_module) #:nodoc:
72
+ master_helper_module.module_eval { include helper_module }
73
+ end
74
+
75
+ # The +helper+ class method can take a series of helper module names, a block, or both.
76
+ #
77
+ # * <tt>*args</tt>: One or more +Modules+, +Strings+ or +Symbols+, or the special symbol <tt>:all</tt>.
78
+ # * <tt>&block</tt>: A block defining helper methods.
79
+ #
80
+ # ==== Examples
81
+ # When the argument is a +String+ or +Symbol+, the method will provide the "_helper" suffix, require the file
82
+ # and include the module in the template class. The second form illustrates how to include custom helpers
83
+ # when working with namespaced controllers, or other cases where the file containing the helper definition is not
84
+ # in one of Rails' standard load paths:
85
+ # helper :foo # => requires 'foo_helper' and includes FooHelper
86
+ # helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper
87
+ #
88
+ # When the argument is a +Module+, it will be included directly in the template class.
89
+ # helper FooHelper # => includes FooHelper
90
+ #
91
+ # When the argument is the symbol <tt>:all</tt>, the controller will include all helpers from
92
+ # <tt>app/helpers/**/*.rb</tt> under +RAILS_ROOT+.
93
+ # helper :all
94
+ #
95
+ # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available
96
+ # to the template.
97
+ # # One line
98
+ # helper { def hello() "Hello, world!" end }
99
+ # # Multi-line
100
+ # helper do
101
+ # def foo(bar)
102
+ # "#{bar} is the very best"
103
+ # end
104
+ # end
105
+ #
106
+ # Finally, all the above styles can be mixed together, and the +helper+ method can be invoked with a mix of
107
+ # +symbols+, +strings+, +modules+ and blocks.
108
+ # helper(:three, BlindHelper) { def mice() 'mice' end }
109
+ #
110
+ def helper(*args, &block)
111
+ args.flatten.each do |arg|
112
+ case arg
113
+ when Module
114
+ add_template_helper(arg)
115
+ when :all
116
+ helper(all_application_helpers)
117
+ when String, Symbol
118
+ file_name = arg.to_s.underscore + '_helper'
119
+ class_name = file_name.camelize
120
+
121
+ begin
122
+ require_dependency(file_name)
123
+ rescue LoadError => load_error
124
+ requiree = / -- (.*?)(\.rb)?$/.match(load_error.message).to_a[1]
125
+ if requiree == file_name
126
+ msg = "Missing helper file helpers/#{file_name}.rb"
127
+ raise LoadError.new(msg).copy_blame!(load_error)
128
+ else
129
+ raise
130
+ end
131
+ end
132
+
133
+ add_template_helper(class_name.constantize)
134
+ else
135
+ raise ArgumentError, "helper expects String, Symbol, or Module argument (was: #{args.inspect})"
136
+ end
137
+ end
138
+
139
+ # Evaluate block in template class if given.
140
+ master_helper_module.module_eval(&block) if block_given?
141
+ end
142
+
143
+ # Declare a controller method as a helper. For example, the following
144
+ # makes the +current_user+ controller method available to the view:
145
+ # class ApplicationController < ActionController::Base
146
+ # helper_method :current_user
147
+ # def current_user
148
+ # @current_user ||= User.find(session[:user])
149
+ # end
150
+ # end
151
+ def helper_method(*methods)
152
+ methods.flatten.each do |method|
153
+ master_helper_module.module_eval <<-end_eval
154
+ def #{method}(*args, &block)
155
+ controller.send(%(#{method}), *args, &block)
156
+ end
157
+ end_eval
158
+ end
159
+ end
160
+
161
+ # Declares helper accessors for controller attributes. For example, the
162
+ # following adds new +name+ and <tt>name=</tt> instance methods to a
163
+ # controller and makes them available to the view:
164
+ # helper_attr :name
165
+ # attr_accessor :name
166
+ def helper_attr(*attrs)
167
+ attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
168
+ end
169
+
170
+
171
+ private
172
+ def default_helper_module!
173
+ unless name.blank?
174
+ module_name = name.sub(/Controller$|$/, 'Helper')
175
+ module_path = module_name.split('::').map { |m| m.underscore }.join('/')
176
+ require_dependency module_path
177
+ helper module_name.constantize
178
+ end
179
+ rescue MissingSourceFile => e
180
+ raise unless e.is_missing? module_path
181
+ rescue NameError => e
182
+ raise unless e.missing_name? module_name
183
+ end
184
+
185
+ def inherited_with_helper(child)
186
+ inherited_without_helper(child)
187
+
188
+ begin
189
+ child.master_helper_module = Module.new
190
+ child.master_helper_module.send! :include, master_helper_module
191
+ child.send! :default_helper_module!
192
+ rescue MissingSourceFile => e
193
+ raise unless e.is_missing?("helpers/#{child.controller_path}_helper")
194
+ end
195
+ end
196
+
197
+ # Extract helper names from files in app/helpers/**/*.rb
198
+ def all_application_helpers
199
+ extract = /^#{Regexp.quote(HELPERS_DIR)}\/?(.*)_helper.rb$/
200
+ Dir["#{HELPERS_DIR}/**/*_helper.rb"].map { |file| file.sub extract, '\1' }
201
+ end
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,326 @@
1
+ module Reactive::Controller #:nodoc:
2
+ module Layout #:nodoc:
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ base.class_eval do
6
+ # NOTE: Can't use alias_method_chain here because +render_without_layout+ is already
7
+ # defined as a publicly exposed method
8
+ alias_method :render_with_no_layout, :render
9
+ alias_method :render, :render_with_a_layout
10
+
11
+ class << self
12
+ alias_method_chain :inherited, :layout
13
+ end
14
+ end
15
+ end
16
+
17
+ # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
18
+ # repeated setups. The inclusion pattern has pages that look like this:
19
+ #
20
+ # <%= render "shared/header" %>
21
+ # Hello World
22
+ # <%= render "shared/footer" %>
23
+ #
24
+ # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose
25
+ # and if you ever want to change the structure of these two includes, you'll have to change all the templates.
26
+ #
27
+ # With layouts, you can flip it around and have the common structure know where to insert changing content. This means
28
+ # that the header and footer are only mentioned in one place, like this:
29
+ #
30
+ # // The header part of this layout
31
+ # <%= yield %>
32
+ # // The footer part of this layout -->
33
+ #
34
+ # And then you have content pages that look like this:
35
+ #
36
+ # hello world
37
+ #
38
+ # Not a word about common structures. At rendering time, the content page is computed and then inserted in the layout,
39
+ # like this:
40
+ #
41
+ # // The header part of this layout
42
+ # hello world
43
+ # // The footer part of this layout -->
44
+ #
45
+ # == Accessing shared variables
46
+ #
47
+ # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with
48
+ # references that won't materialize before rendering time:
49
+ #
50
+ # <h1><%= @page_title %></h1>
51
+ # <%= yield %>
52
+ #
53
+ # ...and content pages that fulfill these references _at_ rendering time:
54
+ #
55
+ # <% @page_title = "Welcome" %>
56
+ # Off-world colonies offers you a chance to start a new life
57
+ #
58
+ # The result after rendering is:
59
+ #
60
+ # <h1>Welcome</h1>
61
+ # Off-world colonies offers you a chance to start a new life
62
+ #
63
+ # == Automatic layout assignment
64
+ #
65
+ # If there is a template in <tt>app/views/layouts/</tt> with the same name as the current controller then it will be automatically
66
+ # set as that controller's layout unless explicitly told otherwise. Say you have a WeblogController, for example. If a template named
67
+ # <tt>app/views/layouts/weblog.erb</tt> or <tt>app/views/layouts/weblog.builder</tt> exists then it will be automatically set as
68
+ # the layout for your WeblogController. You can create a layout with the name <tt>application.erb</tt> or <tt>application.builder</tt>
69
+ # and this will be set as the default controller if there is no layout with the same name as the current controller and there is
70
+ # no layout explicitly assigned with the +layout+ method. Nested controllers use the same folder structure for automatic layout.
71
+ # assignment. So an Admin::WeblogController will look for a template named <tt>app/views/layouts/admin/weblog.erb</tt>.
72
+ # Setting a layout explicitly will always override the automatic behaviour for the controller where the layout is set.
73
+ # Explicitly setting the layout in a parent class, though, will not override the child class's layout assignment if the child
74
+ # class has a layout with the same name.
75
+ #
76
+ # == Inheritance for layouts
77
+ #
78
+ # Layouts are shared downwards in the inheritance hierarchy, but not upwards. Examples:
79
+ #
80
+ # class BankController < ActionController::Base
81
+ # layout "bank_standard"
82
+ #
83
+ # class InformationController < BankController
84
+ #
85
+ # class VaultController < BankController
86
+ # layout :access_level_layout
87
+ #
88
+ # class EmployeeController < BankController
89
+ # layout nil
90
+ #
91
+ # The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites
92
+ # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all.
93
+ #
94
+ # == Types of layouts
95
+ #
96
+ # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes
97
+ # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can
98
+ # be done either by specifying a method reference as a symbol or using an inline method (as a proc).
99
+ #
100
+ # The method reference is the preferred approach to variable layouts and is used like this:
101
+ #
102
+ # class WeblogController < ActionController::Base
103
+ # layout :writers_and_readers
104
+ #
105
+ # def index
106
+ # # fetching posts
107
+ # end
108
+ #
109
+ # private
110
+ # def writers_and_readers
111
+ # logged_in? ? "writer_layout" : "reader_layout"
112
+ # end
113
+ #
114
+ # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
115
+ # is logged in or not.
116
+ #
117
+ # If you want to use an inline method, such as a proc, do something like this:
118
+ #
119
+ # class WeblogController < ActionController::Base
120
+ # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
121
+ #
122
+ # Of course, the most common way of specifying a layout is still just as a plain template name:
123
+ #
124
+ # class WeblogController < ActionController::Base
125
+ # layout "weblog_standard"
126
+ #
127
+ # If no directory is specified for the template name, the template will by default be looked for in +app/views/layouts/+.
128
+ # Otherwise, it will be looked up relative to the template root.
129
+ #
130
+ # == Conditional layouts
131
+ #
132
+ # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering
133
+ # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The
134
+ # <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example:
135
+ #
136
+ # class WeblogController < ActionController::Base
137
+ # layout "weblog_standard", :except => :rss
138
+ #
139
+ # # ...
140
+ #
141
+ # end
142
+ #
143
+ # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout
144
+ # around the rendered view.
145
+ #
146
+ # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
147
+ # #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>.
148
+ #
149
+ # == Using a different layout in the action render call
150
+ #
151
+ # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
152
+ # Some times you'll have exceptions, though, where one action wants to use a different layout than the rest of the controller.
153
+ # This is possible using the <tt>render</tt> method. It's just a bit more manual work as you'll have to supply fully
154
+ # qualified template and layout names as this example shows:
155
+ #
156
+ # class WeblogController < ActionController::Base
157
+ # def help
158
+ # render :action => "help/index", :layout => "help"
159
+ # end
160
+ # end
161
+ #
162
+ # As you can see, you pass the template as the first parameter, the status code as the second ("200" is OK), and the layout
163
+ # as the third.
164
+ #
165
+ # NOTE: The old notation for rendering the view from a layout was to expose the magic <tt>@content_for_layout</tt> instance
166
+ # variable. The preferred notation now is to use <tt>yield</tt>, as documented above.
167
+ module ClassMethods
168
+ # If a layout is specified, all rendered actions will have their result rendered
169
+ # when the layout <tt>yield</tt>s. This layout can itself depend on instance variables assigned during action
170
+ # performance and have access to them as any normal template would.
171
+ def layout(template_name, conditions = {}, auto = false)
172
+ add_layout_conditions(conditions)
173
+ write_inheritable_attribute "layout", template_name
174
+ write_inheritable_attribute "auto_layout", auto
175
+ end
176
+
177
+ def layout_conditions #:nodoc:
178
+ @layout_conditions ||= read_inheritable_attribute("layout_conditions")
179
+ end
180
+
181
+ def default_layout(format) #:nodoc:
182
+ layout = read_inheritable_attribute("layout")
183
+ return layout unless read_inheritable_attribute("auto_layout")
184
+ @default_layout ||= {}
185
+ @default_layout[format] ||= default_layout_with_format(format, layout)
186
+ @default_layout[format]
187
+ end
188
+
189
+ def layout_list #:nodoc:
190
+ view_paths.collect do |path|
191
+ Dir["#{path}/layouts/**/*"]
192
+ end.flatten
193
+ end
194
+
195
+ private
196
+ def inherited_with_layout(child)
197
+ inherited_without_layout(child)
198
+ unless child.name.blank?
199
+ layout_match = child.name.underscore.sub(/_controller$/, '').sub(/^controllers\//, '')
200
+ child.layout(layout_match, {}, true) unless child.layout_list.grep(%r{layouts/#{layout_match}(\.[a-z][0-9a-z]*)+$}).empty?
201
+ end
202
+ end
203
+
204
+ def add_layout_conditions(conditions)
205
+ write_inheritable_hash "layout_conditions", normalize_conditions(conditions)
206
+ end
207
+
208
+ def normalize_conditions(conditions)
209
+ conditions.inject({}) {|hash, (key, value)| hash.merge(key => [value].flatten.map {|action| action.to_s})}
210
+ end
211
+
212
+ def layout_directory_exists_cache
213
+ @@layout_directory_exists_cache ||= Hash.new do |h, dirname|
214
+ h[dirname] = File.directory? dirname
215
+ end
216
+ end
217
+
218
+ def default_layout_with_format(format, layout)
219
+ list = layout_list
220
+ if list.grep(%r{layouts/#{layout}\.#{format}(\.[a-z][0-9a-z]*)+$}).empty?
221
+ (!list.grep(%r{layouts/#{layout}\.([a-z][0-9a-z]*)+$}).empty? && format == :rb) ? layout : nil
222
+ else
223
+ layout
224
+ end
225
+ end
226
+ end
227
+
228
+ # Returns the name of the active layout. If the layout was specified as a method reference (through a symbol), this method
229
+ # is called and the return value is used. Likewise if the layout was specified as an inline method (through a proc or method
230
+ # object). If the layout was defined without a directory, layouts is assumed. So <tt>layout "weblog/standard"</tt> will return
231
+ # weblog/standard, but <tt>layout "standard"</tt> will return layouts/standard.
232
+ def active_layout(passed_layout = nil)
233
+ layout = passed_layout || self.class.default_layout(response.template.template_format)
234
+ active_layout = case layout
235
+ when String then layout
236
+ when Symbol then send!(layout)
237
+ when Proc then layout.call(self)
238
+ end
239
+
240
+ # Explicitly passed layout names with slashes are looked up relative to the template root,
241
+ # but auto-discovered layouts derived from a nested controller will contain a slash, though be relative
242
+ # to the 'layouts' directory so we have to check the file system to infer which case the layout name came from.
243
+ if active_layout
244
+ if active_layout.include?('/') && ! layout_directory?(active_layout)
245
+ active_layout
246
+ else
247
+ "layouts/#{active_layout}"
248
+ end
249
+ end
250
+ end
251
+
252
+ protected
253
+ def render_with_a_layout(options = nil, &block) #:nodoc:
254
+ template_with_options = options.is_a?(Hash)
255
+
256
+ if apply_layout?(template_with_options, options) && (layout = pick_layout(template_with_options, options))
257
+ assert_existence_of_template_file(layout)
258
+
259
+ options = options.merge :layout => false if template_with_options
260
+ logger.info("Rendering template within #{layout}") if logger
261
+
262
+ content_for_layout = render_with_no_layout(options, &block)
263
+ # erase_render_results
264
+ add_variables_to_assigns
265
+ @template.instance_variable_set("@content_for_layout", content_for_layout)
266
+ # response.layout = layout
267
+ status = template_with_options ? options[:status] : nil
268
+ render_for_text(@template.render_file(layout, true), status)
269
+ else
270
+ render_with_no_layout(options, &block)
271
+ end
272
+ end
273
+
274
+
275
+ private
276
+ def apply_layout?(template_with_options, options)
277
+ return false if options == :update
278
+ template_with_options ? candidate_for_layout?(options) : !template_exempt_from_layout?
279
+ end
280
+
281
+ def candidate_for_layout?(options)
282
+ (options.has_key?(:layout) && options[:layout] != false) ||
283
+ options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing).compact.empty? &&
284
+ !template_exempt_from_layout?(options[:template] || default_template_name(options[:action]))
285
+ end
286
+
287
+ def pick_layout(template_with_options, options)
288
+ if template_with_options
289
+ case layout = options[:layout]
290
+ when FalseClass
291
+ nil
292
+ when NilClass, TrueClass
293
+ active_layout if action_has_layout?
294
+ else
295
+ active_layout(layout)
296
+ end
297
+ else
298
+ active_layout if action_has_layout?
299
+ end
300
+ end
301
+
302
+ def action_has_layout?
303
+ if conditions = self.class.layout_conditions
304
+ case
305
+ when only = conditions[:only]
306
+ only.include?(action_name)
307
+ when except = conditions[:except]
308
+ !except.include?(action_name)
309
+ else
310
+ true
311
+ end
312
+ else
313
+ true
314
+ end
315
+ end
316
+
317
+ # Does a layout directory for this class exist?
318
+ # we cache this info in a class level hash
319
+ def layout_directory?(layout_name)
320
+ view_paths.find do |path|
321
+ next unless template_path = Dir[File.join(path, 'layouts', layout_name) + ".*"].first
322
+ self.class.send!(:layout_directory_exists_cache)[File.dirname(template_path)]
323
+ end
324
+ end
325
+ end
326
+ end