reactive-mvc 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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