reactive-mvc 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/LICENSE +21 -0
  2. data/Manifest +34 -0
  3. data/README +30 -0
  4. data/Rakefile +5 -0
  5. data/lib/reactive-mvc.rb +26 -0
  6. data/lib/reactive-mvc/controller.rb +16 -0
  7. data/lib/reactive-mvc/controller/base.rb +405 -0
  8. data/lib/reactive-mvc/controller/filters.rb +767 -0
  9. data/lib/reactive-mvc/controller/flash.rb +161 -0
  10. data/lib/reactive-mvc/controller/helpers.rb +203 -0
  11. data/lib/reactive-mvc/controller/layout.rb +285 -0
  12. data/lib/reactive-mvc/controller/output.rb +262 -0
  13. data/lib/reactive-mvc/controller/rescue.rb +208 -0
  14. data/lib/reactive-mvc/dispatcher.rb +133 -0
  15. data/lib/reactive-mvc/view.rb +18 -0
  16. data/lib/reactive-mvc/view/base.rb +388 -0
  17. data/lib/reactive-mvc/view/helpers.rb +38 -0
  18. data/lib/reactive-mvc/view/partials.rb +207 -0
  19. data/lib/reactive-mvc/view/paths.rb +125 -0
  20. data/lib/reactive-mvc/view/renderable.rb +98 -0
  21. data/lib/reactive-mvc/view/renderable_partial.rb +49 -0
  22. data/lib/reactive-mvc/view/template.rb +110 -0
  23. data/lib/reactive-mvc/view/template_error.rb +9 -0
  24. data/lib/reactive-mvc/view/template_handler.rb +9 -0
  25. data/lib/reactive-mvc/view/template_handlers.rb +43 -0
  26. data/lib/reactive-mvc/view/template_handlers/builder.rb +15 -0
  27. data/lib/reactive-mvc/view/template_handlers/erb.rb +20 -0
  28. data/lib/reactive-mvc/view/template_handlers/ruby_code.rb +9 -0
  29. data/reactive_app_generators/mvc/USAGE +10 -0
  30. data/reactive_app_generators/mvc/mvc_generator.rb +48 -0
  31. data/reactive_app_generators/mvc/templates/application_controller.rb +2 -0
  32. data/reactive_generators/controller/USAGE +7 -0
  33. data/reactive_generators/controller/controller_generator.rb +24 -0
  34. data/reactive_generators/controller/templates/controller.rb +2 -0
  35. metadata +113 -0
@@ -0,0 +1,161 @@
1
+ module Reactive::Mvc::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,203 @@
1
+ # FIXME: helper { ... } is broken on Ruby 1.9
2
+ module Reactive::Mvc::Controller #:nodoc:
3
+ module Helpers #:nodoc:
4
+
5
+ def self.included(base)
6
+ # Initialize the base module to aggregate its helpers.
7
+ base.class_inheritable_accessor :master_helper_module
8
+ base.master_helper_module = Module.new
9
+
10
+ # Extend base with class methods to declare helpers.
11
+ base.extend(ClassMethods)
12
+
13
+ base.class_eval do
14
+ # Wrap inherited to create a new master helper module for subclasses.
15
+ class << self
16
+ alias_method_chain :inherited, :helper
17
+ end
18
+ end
19
+ end
20
+
21
+ # The Rails framework provides a large number of helpers for working with +assets+, +dates+, +forms+,
22
+ # +numbers+ and +ActiveRecord+ objects, to name a few. These helpers are available to all templates
23
+ # by default.
24
+ #
25
+ # In addition to using the standard template helpers provided in the Rails framework, creating custom helpers to
26
+ # extract complicated logic or reusable functionality is strongly encouraged. By default, the controller will
27
+ # include a helper whose name matches that of the controller, e.g., <tt>MyController</tt> will automatically
28
+ # include <tt>MyHelper</tt>.
29
+ #
30
+ # Additional helpers can be specified using the +helper+ class method in <tt>ActionController::Base</tt> or any
31
+ # controller which inherits from it.
32
+ #
33
+ # ==== Examples
34
+ # The +to_s+ method from the +Time+ class can be wrapped in a helper method to display a custom message if
35
+ # the Time object is blank:
36
+ #
37
+ # module FormattedTimeHelper
38
+ # def format_time(time, format=:long, blank_message="&nbsp;")
39
+ # time.blank? ? blank_message : time.to_s(format)
40
+ # end
41
+ # end
42
+ #
43
+ # +FormattedTimeHelper+ can now be included in a controller, using the +helper+ class method:
44
+ #
45
+ # class EventsController < ActionController::Base
46
+ # helper FormattedTimeHelper
47
+ # def index
48
+ # @events = Event.find(:all)
49
+ # end
50
+ # end
51
+ #
52
+ # Then, in any view rendered by <tt>EventController</tt>, the <tt>format_time</tt> method can be called:
53
+ #
54
+ # <% @events.each do |event| -%>
55
+ # <p>
56
+ # <% format_time(event.time, :short, "N/A") %> | <%= event.name %>
57
+ # </p>
58
+ # <% end -%>
59
+ #
60
+ # Finally, assuming we have two event instances, one which has a time and one which does not,
61
+ # the output might look like this:
62
+ #
63
+ # 23 Aug 11:30 | Carolina Railhawks Soccer Match
64
+ # N/A | Carolina Railhaws Training Workshop
65
+ #
66
+ module ClassMethods
67
+ # Makes all the (instance) methods in the helper module available to templates rendered through this controller.
68
+ # See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules
69
+ # available to the templates.
70
+ def add_template_helper(helper_module) #:nodoc:
71
+ master_helper_module.module_eval { include helper_module }
72
+ end
73
+
74
+ # The +helper+ class method can take a series of helper module names, a block, or both.
75
+ #
76
+ # * <tt>*args</tt>: One or more +Modules+, +Strings+ or +Symbols+, or the special symbol <tt>:all</tt>.
77
+ # * <tt>&block</tt>: A block defining helper methods.
78
+ #
79
+ # ==== Examples
80
+ # When the argument is a +String+ or +Symbol+, the method will provide the "_helper" suffix, require the file
81
+ # and include the module in the template class. The second form illustrates how to include custom helpers
82
+ # when working with namespaced controllers, or other cases where the file containing the helper definition is not
83
+ # in one of Rails' standard load paths:
84
+ # helper :foo # => requires 'foo_helper' and includes FooHelper
85
+ # helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper
86
+ #
87
+ # When the argument is a +Module+, it will be included directly in the template class.
88
+ # helper FooHelper # => includes FooHelper
89
+ #
90
+ # When the argument is the symbol <tt>:all</tt>, the controller will include all helpers from
91
+ # <tt>app/helpers/**/*.rb</tt> under +RAILS_ROOT+.
92
+ # helper :all
93
+ #
94
+ # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available
95
+ # to the template.
96
+ # # One line
97
+ # helper { def hello() "Hello, world!" end }
98
+ # # Multi-line
99
+ # helper do
100
+ # def foo(bar)
101
+ # "#{bar} is the very best"
102
+ # end
103
+ # end
104
+ #
105
+ # Finally, all the above styles can be mixed together, and the +helper+ method can be invoked with a mix of
106
+ # +symbols+, +strings+, +modules+ and blocks.
107
+ # helper(:three, BlindHelper) { def mice() 'mice' end }
108
+ #
109
+ def helper(*args, &block)
110
+ args.flatten.each do |arg|
111
+ case arg
112
+ when Module
113
+ add_template_helper(arg)
114
+ when :all
115
+ helper(all_application_helpers)
116
+ when String, Symbol
117
+ file_name = arg.to_s.underscore + '_helper'
118
+ class_name = file_name.camelize
119
+
120
+ begin
121
+ require_dependency(file_name)
122
+ rescue LoadError => load_error
123
+ requiree = / -- (.*?)(\.rb)?$/.match(load_error.message).to_a[1]
124
+ if requiree == file_name
125
+ msg = "Missing helper file helpers/#{file_name}.rb"
126
+ raise LoadError.new(msg).copy_blame!(load_error)
127
+ else
128
+ raise
129
+ end
130
+ end
131
+
132
+ add_template_helper(class_name.constantize)
133
+ else
134
+ raise ArgumentError, "helper expects String, Symbol, or Module argument (was: #{args.inspect})"
135
+ end
136
+ end
137
+
138
+ # Evaluate block in template class if given.
139
+ master_helper_module.module_eval(&block) if block_given?
140
+ end
141
+
142
+ # Declare a controller method as a helper. For example, the following
143
+ # makes the +current_user+ controller method available to the view:
144
+ # class ApplicationController < ActionController::Base
145
+ # helper_method :current_user
146
+ # def current_user
147
+ # @current_user ||= User.find(session[:user])
148
+ # end
149
+ # end
150
+ def helper_method(*methods)
151
+ methods.flatten.each do |method|
152
+ master_helper_module.module_eval <<-end_eval
153
+ def #{method}(*args, &block)
154
+ controller.send(%(#{method}), *args, &block)
155
+ end
156
+ end_eval
157
+ end
158
+ end
159
+
160
+ # Declares helper accessors for controller attributes. For example, the
161
+ # following adds new +name+ and <tt>name=</tt> instance methods to a
162
+ # controller and makes them available to the view:
163
+ # helper_attr :name
164
+ # attr_accessor :name
165
+ def helper_attr(*attrs)
166
+ attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
167
+ end
168
+
169
+
170
+ private
171
+ def default_helper_module!
172
+ unless name.blank?
173
+ module_name = name.sub(/Controller$|$/, 'Helper')
174
+ module_path = module_name.split('::').map { |m| m.underscore }.join('/')
175
+ require_dependency module_path
176
+ helper module_name.constantize
177
+ end
178
+ rescue MissingSourceFile => e
179
+ raise unless e.is_missing? module_path
180
+ rescue NameError => e
181
+ raise unless e.missing_name? module_name
182
+ end
183
+
184
+ def inherited_with_helper(child)
185
+ inherited_without_helper(child)
186
+
187
+ begin
188
+ child.master_helper_module = Module.new
189
+ child.master_helper_module.send :include, master_helper_module
190
+ child.send :default_helper_module!
191
+ rescue MissingSourceFile => e
192
+ raise unless e.is_missing?("helpers/#{child.controller_path}_helper")
193
+ end
194
+ end
195
+
196
+ # Extract helper names from files in app/helpers/**/*.rb
197
+ def all_application_helpers
198
+ extract = /^#{Regexp.quote(Reactive.dir_for(:helper))}\/?(.*)_helper.rb$/
199
+ Dir["#{Reactive.dir_for(:helper)}/**/*_helper.rb"].map { |file| file.sub extract, '\1' }
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,285 @@
1
+ module Reactive::Mvc::Controller #:nodoc:
2
+ module Layout #:nodoc:
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ base.class_eval do
6
+ class << self
7
+ alias_method_chain :inherited, :layout
8
+ end
9
+ end
10
+ end
11
+
12
+ # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
13
+ # repeated setups. The inclusion pattern has pages that look like this:
14
+ #
15
+ # <%= render "shared/header" %>
16
+ # Hello World
17
+ # <%= render "shared/footer" %>
18
+ #
19
+ # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose
20
+ # and if you ever want to change the structure of these two includes, you'll have to change all the templates.
21
+ #
22
+ # With layouts, you can flip it around and have the common structure know where to insert changing content. This means
23
+ # that the header and footer are only mentioned in one place, like this:
24
+ #
25
+ # // The header part of this layout
26
+ # <%= yield %>
27
+ # // The footer part of this layout -->
28
+ #
29
+ # And then you have content pages that look like this:
30
+ #
31
+ # hello world
32
+ #
33
+ # Not a word about common structures. At rendering time, the content page is computed and then inserted in the layout,
34
+ # like this:
35
+ #
36
+ # // The header part of this layout
37
+ # hello world
38
+ # // The footer part of this layout -->
39
+ #
40
+ # == Accessing shared variables
41
+ #
42
+ # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with
43
+ # references that won't materialize before rendering time:
44
+ #
45
+ # <h1><%= @page_title %></h1>
46
+ # <%= yield %>
47
+ #
48
+ # ...and content pages that fulfill these references _at_ rendering time:
49
+ #
50
+ # <% @page_title = "Welcome" %>
51
+ # Off-world colonies offers you a chance to start a new life
52
+ #
53
+ # The result after rendering is:
54
+ #
55
+ # <h1>Welcome</h1>
56
+ # Off-world colonies offers you a chance to start a new life
57
+ #
58
+ # == Automatic layout assignment
59
+ #
60
+ # If there is a template in <tt>app/views/layouts/</tt> with the same name as the current controller then it will be automatically
61
+ # set as that controller's layout unless explicitly told otherwise. Say you have a WeblogController, for example. If a template named
62
+ # <tt>app/views/layouts/weblog.erb</tt> or <tt>app/views/layouts/weblog.builder</tt> exists then it will be automatically set as
63
+ # the layout for your WeblogController. You can create a layout with the name <tt>application.erb</tt> or <tt>application.builder</tt>
64
+ # 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
65
+ # no layout explicitly assigned with the +layout+ method. Nested controllers use the same folder structure for automatic layout.
66
+ # assignment. So an Admin::WeblogController will look for a template named <tt>app/views/layouts/admin/weblog.erb</tt>.
67
+ # Setting a layout explicitly will always override the automatic behaviour for the controller where the layout is set.
68
+ # Explicitly setting the layout in a parent class, though, will not override the child class's layout assignment if the child
69
+ # class has a layout with the same name.
70
+ #
71
+ # == Inheritance for layouts
72
+ #
73
+ # Layouts are shared downwards in the inheritance hierarchy, but not upwards. Examples:
74
+ #
75
+ # class BankController < ActionController::Base
76
+ # layout "bank_standard"
77
+ #
78
+ # class InformationController < BankController
79
+ #
80
+ # class VaultController < BankController
81
+ # layout :access_level_layout
82
+ #
83
+ # class EmployeeController < BankController
84
+ # layout nil
85
+ #
86
+ # The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites
87
+ # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all.
88
+ #
89
+ # == Types of layouts
90
+ #
91
+ # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes
92
+ # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can
93
+ # be done either by specifying a method reference as a symbol or using an inline method (as a proc).
94
+ #
95
+ # The method reference is the preferred approach to variable layouts and is used like this:
96
+ #
97
+ # class WeblogController < ActionController::Base
98
+ # layout :writers_and_readers
99
+ #
100
+ # def index
101
+ # # fetching posts
102
+ # end
103
+ #
104
+ # private
105
+ # def writers_and_readers
106
+ # logged_in? ? "writer_layout" : "reader_layout"
107
+ # end
108
+ #
109
+ # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
110
+ # is logged in or not.
111
+ #
112
+ # If you want to use an inline method, such as a proc, do something like this:
113
+ #
114
+ # class WeblogController < ActionController::Base
115
+ # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
116
+ #
117
+ # Of course, the most common way of specifying a layout is still just as a plain template name:
118
+ #
119
+ # class WeblogController < ActionController::Base
120
+ # layout "weblog_standard"
121
+ #
122
+ # If no directory is specified for the template name, the template will by default be looked for in +app/views/layouts/+.
123
+ # Otherwise, it will be looked up relative to the template root.
124
+ #
125
+ # == Conditional layouts
126
+ #
127
+ # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering
128
+ # 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
129
+ # <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example:
130
+ #
131
+ # class WeblogController < ActionController::Base
132
+ # layout "weblog_standard", :except => :rss
133
+ #
134
+ # # ...
135
+ #
136
+ # end
137
+ #
138
+ # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout
139
+ # around the rendered view.
140
+ #
141
+ # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
142
+ # #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>.
143
+ #
144
+ # == Using a different layout in the action render call
145
+ #
146
+ # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
147
+ # Some times you'll have exceptions, though, where one action wants to use a different layout than the rest of the controller.
148
+ # This is possible using the <tt>render</tt> method. It's just a bit more manual work as you'll have to supply fully
149
+ # qualified template and layout names as this example shows:
150
+ #
151
+ # class WeblogController < ActionController::Base
152
+ # def help
153
+ # render :action => "help/index", :layout => "help"
154
+ # end
155
+ # end
156
+ #
157
+ # As you can see, you pass the template as the first parameter, the status code as the second ("200" is OK), and the layout
158
+ # as the third.
159
+ #
160
+ # NOTE: The old notation for rendering the view from a layout was to expose the magic <tt>@content_for_layout</tt> instance
161
+ # variable. The preferred notation now is to use <tt>yield</tt>, as documented above.
162
+ module ClassMethods
163
+ # If a layout is specified, all rendered actions will have their result rendered
164
+ # when the layout <tt>yield</tt>s. This layout can itself depend on instance variables assigned during action
165
+ # performance and have access to them as any normal template would.
166
+ def layout(template_name, conditions = {}, auto = false)
167
+ add_layout_conditions(conditions)
168
+ write_inheritable_attribute(:layout, template_name)
169
+ write_inheritable_attribute(:auto_layout, auto)
170
+ end
171
+
172
+ def layout_conditions #:nodoc:
173
+ @layout_conditions ||= read_inheritable_attribute(:layout_conditions)
174
+ end
175
+
176
+ def default_layout(format) #:nodoc:
177
+ layout = read_inheritable_attribute(:layout)
178
+ return layout unless read_inheritable_attribute(:auto_layout)
179
+ @default_layout ||= {}
180
+ @default_layout[format] ||= default_layout_with_format(format, layout)
181
+ @default_layout[format]
182
+ end
183
+
184
+ def layout_list #:nodoc:
185
+ Array(view_paths).sum([]) { |path| Dir["#{path}/layouts/**/*"] }
186
+ end
187
+
188
+ private
189
+ def inherited_with_layout(child)
190
+ inherited_without_layout(child)
191
+ unless child.name.blank?
192
+ layout_match = child.name.underscore.sub(/_controller$/, '').sub(/^controllers\//, '')
193
+ child.layout(layout_match, {}, true) unless child.layout_list.grep(%r{layouts/#{layout_match}(\.[a-z][0-9a-z]*)+$}).empty?
194
+ end
195
+ end
196
+
197
+ def add_layout_conditions(conditions)
198
+ write_inheritable_hash(:layout_conditions, normalize_conditions(conditions))
199
+ end
200
+
201
+ def normalize_conditions(conditions)
202
+ conditions.inject({}) {|hash, (key, value)| hash.merge(key => [value].flatten.map {|action| action.to_s})}
203
+ end
204
+
205
+ def default_layout_with_format(format, layout)
206
+ list = layout_list
207
+ if list.grep(%r{layouts/#{layout}\.#{format}(\.[a-z][0-9a-z]*)+$}).empty?
208
+ (!list.grep(%r{layouts/#{layout}\.([a-z][0-9a-z]*)+$}).empty? && format == :rb) ? layout : nil
209
+ else
210
+ layout
211
+ end
212
+ end
213
+ end
214
+
215
+ # Returns the name of the active layout. If the layout was specified as a method reference (through a symbol), this method
216
+ # is called and the return value is used. Likewise if the layout was specified as an inline method (through a proc or method
217
+ # object). If the layout was defined without a directory, layouts is assumed. So <tt>layout "weblog/standard"</tt> will return
218
+ # weblog/standard, but <tt>layout "standard"</tt> will return layouts/standard.
219
+ def active_layout(passed_layout = nil)
220
+ layout = passed_layout || self.class.default_layout(default_template_format)
221
+ active_layout = case layout
222
+ when String then layout
223
+ when Symbol then send(layout)
224
+ when Proc then layout.call(self)
225
+ end
226
+
227
+ # Explicitly passed layout names with slashes are looked up relative to the template root,
228
+ # but auto-discovered layouts derived from a nested controller will contain a slash, though be relative
229
+ # to the 'layouts' directory so we have to check the file system to infer which case the layout name came from.
230
+ if active_layout
231
+ if active_layout.include?('/') && ! layout_directory?(active_layout)
232
+ active_layout
233
+ else
234
+ "layouts/#{active_layout}"
235
+ end
236
+ end
237
+ end
238
+
239
+ private
240
+ def candidate_for_layout?(options)
241
+ options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing, :update).compact.empty? &&
242
+ !@template.__send__(:_exempt_from_layout?, options[:template] || default_template_name(options[:action]))
243
+ end
244
+
245
+ def pick_layout(options)
246
+ if options.has_key?(:layout)
247
+ case layout = options.delete(:layout)
248
+ when FalseClass
249
+ nil
250
+ when NilClass, TrueClass
251
+ active_layout if action_has_layout? && !@template.__send__(:_exempt_from_layout?, default_template_name)
252
+ else
253
+ active_layout(layout)
254
+ end
255
+ else
256
+ active_layout if action_has_layout? && candidate_for_layout?(options)
257
+ end
258
+ end
259
+
260
+ def action_has_layout?
261
+ if conditions = self.class.layout_conditions
262
+ case
263
+ when only = conditions[:only]
264
+ only.include?(action_name)
265
+ when except = conditions[:except]
266
+ !except.include?(action_name)
267
+ else
268
+ true
269
+ end
270
+ else
271
+ true
272
+ end
273
+ end
274
+
275
+ def layout_directory?(layout_name)
276
+ @template.template_exists?("#{File.join('layouts', layout_name)}.#{@template.template_format}")
277
+ rescue Reactive::View::MissingTemplate
278
+ false
279
+ end
280
+
281
+ def default_template_format
282
+ response.template.template_format
283
+ end
284
+ end
285
+ end