diff options
author | Yehuda Katz <wycats@Yehuda-Katz.local> | 2009-12-10 13:11:15 -0800 |
---|---|---|
committer | Yehuda Katz <wycats@Yehuda-Katz.local> | 2009-12-10 13:11:15 -0800 |
commit | 8b9275340f6845f1fd3ad8fc43d66a95a73b0c2d (patch) | |
tree | 2acb83debd9772fb0719dbb9ce23c70c5dd953b5 /actionpack/lib | |
parent | 2ecfa817c95f98247d1bd7fb28b208d3f950b43f (diff) | |
parent | ec5434c3c2631bb47b568eede397c3bd596eeb88 (diff) | |
download | rails-8b9275340f6845f1fd3ad8fc43d66a95a73b0c2d.tar.gz rails-8b9275340f6845f1fd3ad8fc43d66a95a73b0c2d.tar.bz2 rails-8b9275340f6845f1fd3ad8fc43d66a95a73b0c2d.zip |
Merge branch 'master' of github.com:rails/rails
Diffstat (limited to 'actionpack/lib')
-rw-r--r-- | actionpack/lib/action_controller/metal/render_options.rb | 101 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/routing/mapper.rb | 599 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/routing/route_set.rb | 9 |
3 files changed, 360 insertions, 349 deletions
diff --git a/actionpack/lib/action_controller/metal/render_options.rb b/actionpack/lib/action_controller/metal/render_options.rb index 0d69ca10df..b6a7ca0eda 100644 --- a/actionpack/lib/action_controller/metal/render_options.rb +++ b/actionpack/lib/action_controller/metal/render_options.rb @@ -1,19 +1,24 @@ module ActionController + + def self.add_renderer(key, &block) + RenderOptions.add(key, &block) + end + module RenderOptions extend ActiveSupport::Concern included do extlib_inheritable_accessor :_renderers - self._renderers = [] + self._renderers = {} end module ClassMethods def _write_render_options - renderers = _renderers.map do |r| + renderers = _renderers.map do |name, value| <<-RUBY_EVAL - if options.key?(:#{r}) + if options.key?(:#{name}) _process_options(options) - return render_#{r}(options[:#{r}], options) + return _render_option_#{name}(options[:#{name}], options) end RUBY_EVAL end @@ -25,79 +30,63 @@ module ActionController RUBY_EVAL end - def _add_render_option(name) - _renderers << name + def use_renderers(*args) + args.each do |key| + _renderers[key] = RENDERERS[key] + end _write_render_options end + alias use_renderer use_renderers end def render_to_body(options) _handle_render_options(options) || super end - end - - module RenderOption #:nodoc: - def self.extended(base) - base.extend ActiveSupport::Concern - base.send :include, ::ActionController::RenderOptions - def base.register_renderer(name) - included { _add_render_option(name) } - end + RENDERERS = {} + def self.add(key, &block) + define_method("_render_option_#{key}", &block) + RENDERERS[key] = block + All._write_render_options end - end - module RenderOptions - module Json - extend RenderOption - register_renderer :json + module All + extend ActiveSupport::Concern + include RenderOptions - def render_json(json, options) - json = ActiveSupport::JSON.encode(json) unless json.respond_to?(:to_str) - json = "#{options[:callback]}(#{json})" unless options[:callback].blank? - self.content_type ||= Mime::JSON - self.response_body = json + INCLUDED = [] + included do + self._renderers = RENDERERS + _write_render_options + INCLUDED << self end - end - - module Js - extend RenderOption - register_renderer :js - def render_js(js, options) - self.content_type ||= Mime::JS - self.response_body = js.respond_to?(:to_js) ? js.to_js : js + def self._write_render_options + INCLUDED.each(&:_write_render_options) end end - module Xml - extend RenderOption - register_renderer :xml - - def render_xml(xml, options) - self.content_type ||= Mime::XML - self.response_body = xml.respond_to?(:to_xml) ? xml.to_xml : xml - end + add :json do |json, options| + json = ActiveSupport::JSON.encode(json) unless json.respond_to?(:to_str) + json = "#{options[:callback]}(#{json})" unless options[:callback].blank? + self.content_type ||= Mime::JSON + self.response_body = json end - module RJS - extend RenderOption - register_renderer :update - - def render_update(proc, options) - generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(view_context, &proc) - self.content_type = Mime::JS - self.response_body = generator.to_s - end + add :js do |js, options| + self.content_type ||= Mime::JS + self.response_body = js.respond_to?(:to_js) ? js.to_js : js end - module All - extend ActiveSupport::Concern + add :xml do |xml, options| + self.content_type ||= Mime::XML + self.response_body = xml.respond_to?(:to_xml) ? xml.to_xml : xml + end - include ActionController::RenderOptions::Json - include ActionController::RenderOptions::Js - include ActionController::RenderOptions::Xml - include ActionController::RenderOptions::RJS + add :update do |proc, options| + generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(view_context, &proc) + self.content_type = Mime::JS + self.response_body = generator.to_s end end end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 400039353c..3b185c2576 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1,221 +1,193 @@ module ActionDispatch module Routing class Mapper - module Resources - class Resource #:nodoc: - attr_reader :plural, :singular - attr_reader :path_prefix, :name_prefix - - def initialize(entities, options = {}) - entities = entities.to_s - - @plural = entities.pluralize - @singular = entities.singularize - - @path_prefix = options[:path_prefix] - @name_prefix = options[:name_prefix] - end - - def name - plural - end - - def controller - plural + class Constraints + def new(app, constraints = []) + if constraints.any? + super(app, constraints) + else + app end + end - def member_name - if name_prefix - "#{name_prefix}_#{singular}" - else - singular - end - end + def initialize(app, constraints = []) + @app, @constraints = app, constraints + end - def collection_name - if name_prefix - "#{name_prefix}_#{plural}" - else - plural - end - end + def call(env) + req = Rack::Request.new(env) - def new_name - if name_prefix - "new_#{name_prefix}_#{singular}" - else - "new_#{singular}" + @constraints.each { |constraint| + if constraint.respond_to?(:matches?) && !constraint.matches?(req) + return [417, {}, []] + elsif constraint.respond_to?(:call) && !constraint.call(req) + return [417, {}, []] end - end + } - def edit_name - if name_prefix - "edit_#{name_prefix}_#{singular}" - else - "edit_#{singular}" - end - end + @app.call(env) end + end - class SingletonResource < Resource #:nodoc: - def initialize(entity, options = {}) - super(entity) - end + module Base + def initialize(set) + @set = set + end - def name - singular - end + def root(options = {}) + match '/', options.merge(:as => :root) end - def resource(*resources, &block) - options = resources.extract_options! + def match(*args) + options = args.extract_options! - if resources.length > 1 - raise ArgumentError if block_given? - resources.each { |r| resource(r, options) } - return self - end + path = args.first - name_prefix = @scope[:options][:name_prefix] if @scope[:options] - resource = SingletonResource.new(resources.pop, :name_prefix => name_prefix) + conditions, defaults = {}, {} - if @scope[:scope_level] == :resources - parent_resource = @scope[:scope_level_options][:name] - parent_named_prefix = @scope[:scope_level_options][:name_prefix] - with_scope_level(:member) do - scope(":#{parent_resource}_id", :name_prefix => parent_named_prefix) do - resource(resource.name, options, &block) - end - end - return self - end + path = nil if path == "" + path = "#{@scope[:path]}#{path}" if @scope[:path] + path = Rack::Mount::Utils.normalize_path(path) if path - controller(resource.controller) do - namespace(resource.name) do - with_scope_level(:resource, :name => resource.singular, :name_prefix => resource.member_name) do - yield if block_given? + raise ArgumentError, "path is required" unless path - get "", :to => :show, :as => resource.member_name - post "", :to => :create - put "", :to => :update - delete "", :to => :destroy - get "new", :to => :new, :as => resource.new_name - get "edit", :to => :edit, :as => resource.edit_name - end - end + constraints = options[:constraints] || {} + unless constraints.is_a?(Hash) + block, constraints = constraints, {} end + blocks = ((@scope[:blocks] || []) + [block]).compact + constraints = (@scope[:constraints] || {}).merge(constraints) + options.each { |k, v| constraints[k] = v if v.is_a?(Regexp) } - self - end + conditions[:path_info] = path + requirements = constraints.dup - def resources(*resources, &block) - options = resources.extract_options! + path_regexp = Rack::Mount::Strexp.compile(path, constraints, SEPARATORS) + segment_keys = Rack::Mount::RegexpWithNamedGroups.new(path_regexp).names + constraints.reject! { |k, v| segment_keys.include?(k.to_s) } + conditions.merge!(constraints) - if resources.length > 1 - raise ArgumentError if block_given? - resources.each { |r| resources(r, options) } - return self - end - - name_prefix = @scope[:options][:name_prefix] if @scope[:options] - resource = Resource.new(resources.pop, :name_prefix => name_prefix) + requirements[:controller] ||= @set.controller_constraints - if @scope[:scope_level] == :resources - parent_resource = @scope[:scope_level_options][:name] - parent_named_prefix = @scope[:scope_level_options][:name_prefix] - with_scope_level(:member) do - scope(":#{parent_resource}_id", :name_prefix => parent_named_prefix) do - resources(resource.name, options, &block) - end - end - return self + if via = options[:via] + via = Array(via).map { |m| m.to_s.upcase } + conditions[:request_method] = Regexp.union(*via) end - controller(resource.controller) do - namespace(resource.name) do - with_scope_level(:resources, :name => resource.singular, :name_prefix => resource.member_name) do - yield if block_given? + defaults[:controller] ||= @scope[:controller].to_s if @scope[:controller] - collection do - get "", :to => :index, :as => resource.collection_name - post "", :to => :create - get "new", :to => :new, :as => resource.new_name - end + app = initialize_app_endpoint(options, defaults) + validate_defaults!(app, defaults, segment_keys) + app = Constraints.new(app, blocks) - member do - get "", :to => :show, :as => resource.member_name - put "", :to => :update - delete "", :to => :destroy - get "edit", :to => :edit, :as => resource.edit_name - end - end - end - end + @set.add_route(app, conditions, requirements, defaults, options[:as]) self end - def collection - unless @scope[:scope_level] == :resources - raise ArgumentError, "can't use collection outside resources scope" - end + private + def initialize_app_endpoint(options, defaults) + app = nil + + if options[:to].respond_to?(:call) + app = options[:to] + defaults.delete(:controller) + defaults.delete(:action) + elsif options[:to].is_a?(String) + defaults[:controller], defaults[:action] = options[:to].split('#') + elsif options[:to].is_a?(Symbol) + defaults[:action] = options[:to].to_s + end - with_scope_level(:collection) do - yield + app || Routing::RouteSet::Dispatcher.new(:defaults => defaults) end - end - def member - unless @scope[:scope_level] == :resources - raise ArgumentError, "can't use member outside resources scope" - end + def validate_defaults!(app, defaults, segment_keys) + return unless app.is_a?(Routing::RouteSet::Dispatcher) - with_scope_level(:member) do - scope(":id") do - yield + unless defaults.include?(:controller) || segment_keys.include?("controller") + raise ArgumentError, "missing :controller" + end + + unless defaults.include?(:action) || segment_keys.include?("action") + raise ArgumentError, "missing :action" end end + end + + module HttpHelpers + def get(*args, &block) + map_method(:get, *args, &block) end - def match(*args) - options = args.extract_options! - args.push(options) + def post(*args, &block) + map_method(:post, *args, &block) + end - case options.delete(:on) - when :collection - return collection { match(*args) } - when :member - return member { match(*args) } - end + def put(*args, &block) + map_method(:put, *args, &block) + end - if @scope[:scope_level] == :resources - raise ArgumentError, "can't define route directly in resources scope" - end + def delete(*args, &block) + map_method(:delete, *args, &block) + end - super + def redirect(path, options = {}) + status = options[:status] || 301 + lambda { |env| + req = Rack::Request.new(env) + url = req.scheme + '://' + req.host + path + [status, {'Location' => url, 'Content-Type' => 'text/html'}, ['Moved Permanently']] + } end private - def with_scope_level(kind, options = {}) - old, @scope[:scope_level] = @scope[:scope_level], kind - old_options, @scope[:scope_level_options] = @scope[:scope_level_options], options - yield - ensure - @scope[:scope_level] = old - @scope[:scope_level_options] = old_options + def map_method(method, *args, &block) + options = args.extract_options! + options[:via] = method + args.push(options) + match(*args, &block) + self end end module Scoping - def self.extended(object) - object.instance_eval do - @scope = {} - end + def initialize(*args) + @scope = {} + super end def scope(*args) options = args.extract_options! + case args.first + when String + options[:path] = args.first + when Symbol + options[:controller] = args.first + end + + if path = options.delete(:path) + path_set = true + path, @scope[:path] = @scope[:path], Rack::Mount::Utils.normalize_path(@scope[:path].to_s + path.to_s) + else + path_set = false + end + + if name_prefix = options.delete(:name_prefix) + name_prefix_set = true + name_prefix, @scope[:name_prefix] = @scope[:name_prefix], (@scope[:name_prefix] ? "#{@scope[:name_prefix]}_#{name_prefix}" : name_prefix) + else + name_prefix_set = false + end + + if controller = options.delete(:controller) + controller_set = true + controller, @scope[:controller] = @scope[:controller], controller + else + controller_set = false + end + constraints = options.delete(:constraints) || {} unless constraints.is_a?(Hash) block, constraints = constraints, {} @@ -225,24 +197,12 @@ module ActionDispatch options, @scope[:options] = @scope[:options], (@scope[:options] || {}).merge(options) - path_set = controller_set = false - - case args.first - when String - path_set = true - path = args.first - path, @scope[:path] = @scope[:path], "#{@scope[:path]}#{Rack::Mount::Utils.normalize_path(path)}" - when Symbol - controller_set = true - controller = args.first - controller, @scope[:controller] = @scope[:controller], controller - end - yield self ensure @scope[:path] = path if path_set + @scope[:name_prefix] = name_prefix if name_prefix_set @scope[:controller] = controller if controller_set @scope[:options] = options @scope[:blocks] = blocks @@ -254,7 +214,7 @@ module ActionDispatch end def namespace(path) - scope(path.to_s) { yield } + scope("/#{path}") { yield } end def constraints(constraints = {}) @@ -263,172 +223,227 @@ module ActionDispatch def match(*args) options = args.extract_options! + options = (@scope[:options] || {}).merge(options) + + if @scope[:name_prefix] && !options[:as].blank? + options[:as] = "#{@scope[:name_prefix]}_#{options[:as]}" + elsif @scope[:name_prefix] && options[:as] == "" + options[:as] = @scope[:name_prefix].to_s + end + args.push(options) super(*args) end end - module HttpHelpers - def get(*args, &block) - map_method(:get, *args, &block) - end + module Resources + class Resource #:nodoc: + attr_reader :plural, :singular - def post(*args, &block) - map_method(:post, *args, &block) - end + def initialize(entities, options = {}) + entities = entities.to_s - def put(*args, &block) - map_method(:put, *args, &block) - end + @plural = entities.pluralize + @singular = entities.singularize + end - def delete(*args, &block) - map_method(:delete, *args, &block) - end + def name + plural + end - def redirect(path, options = {}) - status = options[:status] || 301 - lambda { |env| - req = Rack::Request.new(env) - url = req.scheme + '://' + req.host + path - [status, {'Location' => url, 'Content-Type' => 'text/html'}, ['Moved Permanently']] - } - end + def controller + plural + end - private - def map_method(method, *args, &block) - options = args.extract_options! - options[:via] = method - args.push(options) - match(*args, &block) - self + def member_name + singular end - end - class Constraints - def new(app, constraints = []) - if constraints.any? - super(app, constraints) - else - app + def collection_name + plural + end + + def id_segment + ":#{singular}_id" end end - def initialize(app, constraints = []) - @app, @constraints = app, constraints + class SingletonResource < Resource #:nodoc: + def initialize(entity, options = {}) + super + end + + def name + singular + end end - def call(env) - req = Rack::Request.new(env) + def resource(*resources, &block) + options = resources.extract_options! - @constraints.each { |constraint| - if constraint.respond_to?(:matches?) && !constraint.matches?(req) - return [417, {}, []] - elsif constraint.respond_to?(:call) && !constraint.call(req) - return [417, {}, []] + if resources.length > 1 + raise ArgumentError if block_given? + resources.each { |r| resource(r, options) } + return self + end + + resource = SingletonResource.new(resources.pop) + + if @scope[:scope_level] == :resources + nested do + resource(resource.name, options, &block) end - } + return self + end - @app.call(env) - end - end + scope(:path => "/#{resource.name}", :controller => resource.controller) do + with_scope_level(:resource, resource) do + yield if block_given? - def initialize(set) - @set = set + get "(.:format)", :to => :show, :as => resource.member_name + post "(.:format)", :to => :create + put "(.:format)", :to => :update + delete "(.:format)", :to => :destroy + get "/new(.:format)", :to => :new, :as => "new_#{resource.singular}" + get "/edit(.:format)", :to => :edit, :as => "edit_#{resource.singular}" + end + end - extend HttpHelpers - extend Scoping - extend Resources - end + self + end - def root(options = {}) - match '/', options.merge(:as => :root) - end + def resources(*resources, &block) + options = resources.extract_options! - def match(*args) - options = args.extract_options! + if resources.length > 1 + raise ArgumentError if block_given? + resources.each { |r| resources(r, options) } + return self + end - if args.length > 1 - args.each { |path| match(path, options) } - return self - end + resource = Resource.new(resources.pop) - if args.first.is_a?(Symbol) - return match(args.first.to_s, options.merge(:to => args.first.to_sym)) - end + if @scope[:scope_level] == :resources + nested do + resources(resource.name, options, &block) + end + return self + end - path = args.first + scope(:path => "/#{resource.name}", :controller => resource.controller) do + with_scope_level(:resources, resource) do + yield if block_given? - conditions, defaults = {}, {} + with_scope_level(:collection) do + get "(.:format)", :to => :index, :as => resource.collection_name + post "(.:format)", :to => :create + get "/new(.:format)", :to => :new, :as => "new_#{resource.singular}" + end - path = nil if path == "" - path = Rack::Mount::Utils.normalize_path(path) if path - path = "#{@scope[:path]}#{path}" if @scope[:path] + with_scope_level(:member) do + scope("/:id") do + get "(.:format)", :to => :show, :as => resource.member_name + put "(.:format)", :to => :update + delete "(.:format)", :to => :destroy + get "/edit(.:format)", :to => :edit, :as => "edit_#{resource.singular}" + end + end + end + end + + self + end - raise ArgumentError, "path is required" unless path + def collection + unless @scope[:scope_level] == :resources + raise ArgumentError, "can't use collection outside resources scope" + end - constraints = options[:constraints] || {} - unless constraints.is_a?(Hash) - block, constraints = constraints, {} + with_scope_level(:collection) do + scope(:name_prefix => parent_resource.collection_name, :as => "") do + yield + end + end end - blocks = ((@scope[:blocks] || []) + [block]).compact - constraints = (@scope[:constraints] || {}).merge(constraints) - options.each { |k, v| constraints[k] = v if v.is_a?(Regexp) } - conditions[:path_info] = path - requirements = constraints.dup + def member + unless @scope[:scope_level] == :resources + raise ArgumentError, "can't use member outside resources scope" + end - path_regexp = Rack::Mount::Strexp.compile(path, constraints, SEPARATORS) - segment_keys = Rack::Mount::RegexpWithNamedGroups.new(path_regexp).names - constraints.reject! { |k, v| segment_keys.include?(k.to_s) } - conditions.merge!(constraints) + with_scope_level(:member) do + scope("/:id", :name_prefix => parent_resource.member_name, :as => "") do + yield + end + end + end - requirements[:controller] ||= @set.controller_constraints + def nested + unless @scope[:scope_level] == :resources + raise ArgumentError, "can't use nested outside resources scope" + end - if via = options[:via] - via = Array(via).map { |m| m.to_s.upcase } - conditions[:request_method] = Regexp.union(*via) + with_scope_level(:nested) do + scope("/#{parent_resource.id_segment}", :name_prefix => parent_resource.member_name) do + yield + end + end end - defaults[:controller] ||= @scope[:controller].to_s if @scope[:controller] + def match(*args) + options = args.extract_options! - app = initialize_app_endpoint(options, defaults) - validate_defaults!(app, defaults, segment_keys) - app = Constraints.new(app, blocks) + if args.length > 1 + args.each { |path| match(path, options) } + return self + end - @set.add_route(app, conditions, requirements, defaults, options[:as]) + if args.first.is_a?(Symbol) + begin + old_name_prefix, @scope[:name_prefix] = @scope[:name_prefix], "#{args.first}_#{@scope[:name_prefix]}" + return match("/#{args.first}(.:format)", options.merge(:to => args.first.to_sym)) + ensure + @scope[:name_prefix] = old_name_prefix + end + end - self - end + args.push(options) - private - def initialize_app_endpoint(options, defaults) - app = nil + case options.delete(:on) + when :collection + return collection { match(*args) } + when :member + return member { match(*args) } + end - if options[:to].respond_to?(:call) - app = options[:to] - defaults.delete(:controller) - defaults.delete(:action) - elsif options[:to].is_a?(String) - defaults[:controller], defaults[:action] = options[:to].split('#') - elsif options[:to].is_a?(Symbol) - defaults[:action] = options[:to].to_s + if @scope[:scope_level] == :resources + raise ArgumentError, "can't define route directly in resources scope" end - app || Routing::RouteSet::Dispatcher.new(:defaults => defaults) + super end - def validate_defaults!(app, defaults, segment_keys) - return unless app.is_a?(Routing::RouteSet::Dispatcher) - - unless defaults.include?(:controller) || segment_keys.include?("controller") - raise ArgumentError, "missing :controller" + protected + def parent_resource + @scope[:scope_level_resource] end - unless defaults.include?(:action) || segment_keys.include?("action") - raise ArgumentError, "missing :action" + private + def with_scope_level(kind, resource = parent_resource) + old, @scope[:scope_level] = @scope[:scope_level], kind + old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource + yield + ensure + @scope[:scope_level] = old + @scope[:scope_level_resource] = old_resource end - end + end + + include Base + include HttpHelpers + include Scoping + include Resources end end end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index a8073c2105..0e83ea3b7e 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -216,7 +216,14 @@ module ActionDispatch def draw(&block) clear! - Mapper.new(self).instance_exec(DeprecatedMapper.new(self), &block) + + mapper = Mapper.new(self) + if block.arity == 1 + mapper.instance_exec(DeprecatedMapper.new(self), &block) + else + mapper.instance_exec(&block) + end + @set.add_route(NotFound) install_helpers @set.freeze |