reactive 0.1.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 (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