reactive 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +3 -0
- data/MIT-LICENSE +21 -0
- data/Manifest.txt +60 -0
- data/README.txt +130 -0
- data/Rakefile +14 -0
- data/app_generators/reactive/USAGE +11 -0
- data/app_generators/reactive/reactive_generator.rb +160 -0
- data/app_generators/reactive/templates/README +130 -0
- data/app_generators/reactive/templates/Rakefile +10 -0
- data/app_generators/reactive/templates/app/controllers/application_controller.rb +2 -0
- data/app_generators/reactive/templates/app/helpers/application_helper.rb +2 -0
- data/app_generators/reactive/templates/config/boot.rb +94 -0
- data/app_generators/reactive/templates/config/databases/frontbase.yml +28 -0
- data/app_generators/reactive/templates/config/databases/mysql.yml +54 -0
- data/app_generators/reactive/templates/config/databases/oracle.yml +39 -0
- data/app_generators/reactive/templates/config/databases/postgresql.yml +48 -0
- data/app_generators/reactive/templates/config/databases/sqlite2.yml +16 -0
- data/app_generators/reactive/templates/config/databases/sqlite3.yml +19 -0
- data/app_generators/reactive/templates/config/empty.log +0 -0
- data/app_generators/reactive/templates/config/environment.rb +11 -0
- data/app_generators/reactive/templates/script/destroy +12 -0
- data/app_generators/reactive/templates/script/generate +12 -0
- data/app_generators/reactive/templates/script/run +5 -0
- data/app_generators/reactive/templates/script/win_script.cmd +1 -0
- data/bin/reactive +16 -0
- data/lib/code_statistics.rb +107 -0
- data/lib/controller.rb +23 -0
- data/lib/controller/base.rb +442 -0
- data/lib/controller/filters.rb +767 -0
- data/lib/controller/flash.rb +161 -0
- data/lib/controller/helpers.rb +204 -0
- data/lib/controller/layout.rb +326 -0
- data/lib/dispatcher.rb +46 -0
- data/lib/generated_attribute.rb +40 -0
- data/lib/initializer.rb +425 -0
- data/lib/named_base_generator.rb +92 -0
- data/lib/reactive.rb +6 -0
- data/lib/request.rb +17 -0
- data/lib/source_annotation_extractor.rb +62 -0
- data/lib/tasks/annotations.rake +23 -0
- data/lib/tasks/databases.rake +347 -0
- data/lib/tasks/log.rake +9 -0
- data/lib/tasks/misc.rake +5 -0
- data/lib/tasks/reactive.rb +16 -0
- data/lib/tasks/statistics.rake +17 -0
- data/lib/tasks/testing.rake +118 -0
- data/lib/version.rb +9 -0
- data/lib/view.rb +1 -0
- data/lib/view/base.rb +33 -0
- data/reactive_generators/model/USAGE +27 -0
- data/reactive_generators/model/model_generator.rb +52 -0
- data/reactive_generators/model/templates/fixtures.yml +19 -0
- data/reactive_generators/model/templates/migration.rb +16 -0
- data/reactive_generators/model/templates/model.rb +2 -0
- data/reactive_generators/model/templates/unit_test.rb +8 -0
- data/reactive_generators/scaffold/USAGE +26 -0
- data/reactive_generators/scaffold/scaffold_generator.rb +75 -0
- data/reactive_generators/scaffold/templates/controller.rb +51 -0
- data/reactive_generators/scaffold/templates/functional_test.rb +49 -0
- data/reactive_generators/scaffold/templates/helper.rb +2 -0
- 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=" ")
|
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
|