diff --git a/Gemfile b/Gemfile index d0b11b04e46..827c67e5224 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ source 'http://rubygems.org' gem "arel", :git => "git://github.com/rails/arel.git" -gem "rack-mount", :git => "git://github.com/rails/rack-mount.git" +#gem "rack-mount", :git => "git://github.com/rails/rack-mount.git" gem "rails", :path => File.dirname(__FILE__) gem "rake", ">= 0.8.7" diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index b17a48003c2..32aba2091a5 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,14 @@ *Rails 3.0.0 [Release Candidate] (unreleased)* +* link_to, button_to, and tag/tag_options now rely on html_escape instead of escape_once. [fxn] + +* url_for returns always unescaped strings, and the :escape option is gone. [fxn] + +* Added accept-charset parameter and _snowman hidden field to force the contents + of Rails POSTed forms to be in UTF-8 [Yehuda Katz] + +* Upgrade to Rack 1.2.1 [Jeremy Kemper] + * Allow :path to be given to match/get/post/put/delete instead of :path_names in the new router [Carlos Antônio da Silva] * Added resources_path_names to the new router DSL [José Valim] @@ -22,7 +31,6 @@ * Removed textilize, textilize_without_paragraph and markdown helpers. [Santiago Pastorino] - *Rails 3.0.0 [beta 4] (June 8th, 2010)* * Remove middleware laziness [José Valim] diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 1dede257f95..b4311599a95 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -23,9 +23,9 @@ Gem::Specification.new do |s| s.add_dependency('activemodel', version) s.add_dependency('builder', '~> 2.1.2') s.add_dependency('i18n', '~> 0.4.1') - s.add_dependency('rack', '~> 1.1.0') + s.add_dependency('rack', '~> 1.2.1') s.add_dependency('rack-test', '~> 0.5.4') - s.add_dependency('rack-mount', '~> 0.6.5') + #s.add_dependency('rack-mount', '~> 0.6.6') s.add_dependency('tzinfo', '~> 0.3.16') s.add_dependency('erubis', '~> 2.6.5') end diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index b6d4fb1284b..89201fb5eec 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -58,12 +58,12 @@ module ActionController module ClassMethods def helpers_dir - ActiveSupport::Deprecation.warn "helpers_dir is deprecated, use helpers_path instead" + ActiveSupport::Deprecation.warn "helpers_dir is deprecated, use helpers_path instead", caller self.helpers_path end def helpers_dir=(value) - ActiveSupport::Deprecation.warn "helpers_dir= is deprecated, use helpers_path= instead" + ActiveSupport::Deprecation.warn "helpers_dir= is deprecated, use helpers_path= instead", caller self.helpers_path = Array(value) end diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb index 6c0331636cb..a8745199786 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb @@ -177,6 +177,7 @@ module HTML #:nodoc: case text when "\\" then value << text + break if scanner.eos? value << scanner.getch when delim break diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb index 064ff3724d8..240dc1890f3 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb @@ -96,6 +96,7 @@ module HTML #:nodoc: while match = @scanner.scan_until(/[\\#{delim}]/) text << match break if @scanner.matched == delim + break if @scanner.eos? text << @scanner.getch # skip the escaped character end end diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index 9b9e81440b3..e9fdf75cc84 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -89,7 +89,7 @@ module ActionDispatch if etag? || last_modified? || !@cache_control.empty? set_conditional_cache_control! elsif nonempty_ok_response? - self.etag = @body + self.etag = body if request && request.etag_matches?(etag) self.status = 304 @@ -137,4 +137,4 @@ module ActionDispatch end end end -end \ No newline at end of file +end diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 0a37bd7fc1e..add8cab2ab0 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -6,7 +6,11 @@ module ActionDispatch module Parameters # Returns both GET and POST \parameters in a single hash. def parameters - @env["action_dispatch.request.parameters"] ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access + @env["action_dispatch.request.parameters"] ||= begin + params = request_parameters.merge(query_parameters) + params.merge!(path_parameters) + encode_params(params).with_indifferent_access + end end alias :params :parameters @@ -32,6 +36,31 @@ module ActionDispatch end private + + # TODO: Validate that the characters are UTF-8. If they aren't, + # you'll get a weird error down the road, but our form handling + # should really prevent that from happening + def encode_params(params) + return params unless "ruby".encoding_aware? + + if params.is_a?(String) + return params.force_encoding("UTF-8").encode! + elsif !params.is_a?(Hash) + return params + end + + params.each do |k, v| + case v + when Hash + encode_params(v) + when Array + v.map! {|el| encode_params(el) } + else + encode_params(v) + end + end + end + # Convert nested Hash to HashWithIndifferentAccess def normalize_parameters(value) case value diff --git a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb index 61c2253763f..bc3f62fb48b 100644 --- a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb +++ b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb @@ -30,6 +30,8 @@ module ActionDispatch class DeprecatedMapper #:nodoc: def initialize(set) #:nodoc: + ActiveSupport::Deprecation.warn "You are using the old router DSL which will be removed in Rails 3.1. " << + "Please check how to update your router file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/" @set = set end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 388f6951870..0b4ba8c9f57 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -131,6 +131,7 @@ module ActionDispatch end defaults[:controller] ||= default_controller + defaults[:action] ||= default_action defaults.delete(:controller) if defaults[:controller].blank? defaults.delete(:action) if defaults[:action].blank? @@ -187,6 +188,12 @@ module ActionDispatch @scope[:controller].to_s end end + + def default_action + if @options[:action] + @options[:action].to_s + end + end end # Invokes Rack::Mount::Utils.normalize path and ensure that @@ -433,10 +440,14 @@ module ActionDispatch end module Resources + # CANONICAL_ACTIONS holds all actions that does not need a prefix or + # a path appended since they fit properly in their scope level. + VALID_ON_OPTIONS = [:new, :collection, :member] + CANONICAL_ACTIONS = [:index, :create, :new, :show, :update, :destroy] + MERGE_FROM_SCOPE_OPTIONS = [:shallow, :constraints] + class Resource #:nodoc: - def self.default_actions - [:index, :create, :new, :show, :update, :destroy, :edit] - end + DEFAULT_ACTIONS = [:index, :create, :new, :show, :update, :destroy, :edit] attr_reader :controller, :path, :options @@ -449,7 +460,7 @@ module ActionDispatch end def default_actions - self.class.default_actions + self.class::DEFAULT_ACTIONS end def actions @@ -533,8 +544,8 @@ module ActionDispatch ["#{path}/:id", options] end - def new_scope - [path] + def new_scope(new_path) + ["#{path}/#{new_path}"] end def nested_scope @@ -543,9 +554,7 @@ module ActionDispatch end class SingletonResource < Resource #:nodoc: - def self.default_actions - [:show, :create, :update, :destroy, :new, :edit] - end + DEFAULT_ACTIONS = [:show, :create, :update, :destroy, :new, :edit] def initialize(entities, options) @name = entities.to_s @@ -591,8 +600,6 @@ module ActionDispatch def resource(*resources, &block) options = resources.extract_options! - options = (@scope[:options] || {}).merge(options) - options[:shallow] = true if @scope[:shallow] && !options.has_key?(:shallow) if apply_common_behavior_for(:resource, resources, options, &block) return self @@ -602,9 +609,12 @@ module ActionDispatch yield if block_given? collection_scope do - post :create if parent_resource.actions.include?(:create) - get :new if parent_resource.actions.include?(:new) - end + post :create + end if parent_resource.actions.include?(:create) + + new_scope do + get :new + end if parent_resource.actions.include?(:new) member_scope do get :show if parent_resource.actions.include?(:show) @@ -619,8 +629,6 @@ module ActionDispatch def resources(*resources, &block) options = resources.extract_options! - options = (@scope[:options] || {}).merge(options) - options[:shallow] = true if @scope[:shallow] && !options.has_key?(:shallow) if apply_common_behavior_for(:resources, resources, options, &block) return self @@ -632,9 +640,12 @@ module ActionDispatch collection_scope do get :index if parent_resource.actions.include?(:index) post :create if parent_resource.actions.include?(:create) - get :new if parent_resource.actions.include?(:new) end + new_scope do + get :new + end if parent_resource.actions.include?(:new) + member_scope do get :show if parent_resource.actions.include?(:show) put :update if parent_resource.actions.include?(:update) @@ -671,12 +682,8 @@ module ActionDispatch raise ArgumentError, "can't use new outside resource(s) scope" end - with_scope_level(:new) do - scope(*parent_resource.new_scope) do - scope(action_path(:new)) do - yield - end - end + new_scope do + yield end end @@ -717,8 +724,7 @@ module ActionDispatch end def match(*args) - options = args.extract_options! - + options = args.extract_options!.dup options[:anchor] = true unless options.key?(:anchor) if args.length > 1 @@ -726,17 +732,12 @@ module ActionDispatch return self end - if [:collection, :member, :new].include?(options[:on]) + on = options.delete(:on) + if VALID_ON_OPTIONS.include?(on) args.push(options) - - case options.delete(:on) - when :collection - return collection { match(*args) } - when :member - return member { match(*args) } - when :new - return new { match(*args) } - end + return send(on){ match(*args) } + elsif on + raise ArgumentError, "Unknown scope #{on.inspect} given to :on" end if @scope[:scope_level] == :resource @@ -745,10 +746,12 @@ module ActionDispatch end path = options.delete(:path) + action = args.first - if args.first.is_a?(Symbol) - path = path_for_action(args.first, path) - options = options_for_action(args.first, options) + if action.is_a?(Symbol) + path = path_for_action(action, path) + options[:to] ||= action + options[:as] = name_for_action(action, options[:as]) with_exclusive_scope do return super(path, options) @@ -786,11 +789,11 @@ module ActionDispatch end protected + def parent_resource #:nodoc: @scope[:scope_level_resource] end - private def apply_common_behavior_for(method, resources, options, &block) if resources.length > 1 resources.each { |r| send(method, r, options, &block) } @@ -804,6 +807,10 @@ module ActionDispatch return true end + scope_options = @scope.slice(*MERGE_FROM_SCOPE_OPTIONS).delete_if{ |k,v| v.blank? } + options.reverse_merge!(scope_options) unless scope_options.empty? + options.reverse_merge!(@scope[:options]) unless @scope[:options].blank? + if resource_scope? nested do send(method, resources.pop, options, &block) @@ -852,6 +859,14 @@ module ActionDispatch end end + def new_scope + with_scope_level(:new) do + scope(*parent_resource.new_scope(action_path(:new))) do + yield + end + end + end + def collection_scope with_scope_level(:collection) do scope(*parent_resource.collection_scope) do @@ -868,48 +883,30 @@ module ActionDispatch end end + def canonical_action?(action, flag) + flag && CANONICAL_ACTIONS.include?(action) + end + + def shallow_scoping? + parent_resource && parent_resource.shallow? && @scope[:scope_level] == :member + end + def path_for_action(action, path) - case action - when :index, :create - "#{@scope[:path]}(.:format)" - when :show, :update, :destroy - if parent_resource.shallow? - "#{@scope[:shallow_path]}/#{parent_resource.path}/:id(.:format)" - else - "#{@scope[:path]}(.:format)" - end - when :new - "#{@scope[:path]}/#{action_path(:new)}(.:format)" - when :edit - if parent_resource.shallow? - "#{@scope[:shallow_path]}/#{parent_resource.path}/:id/#{action_path(:edit)}(.:format)" - else - "#{@scope[:path]}/#{action_path(:edit)}(.:format)" - end + prefix = shallow_scoping? ? + "#{@scope[:shallow_path]}/#{parent_resource.path}/:id" : @scope[:path] + + if canonical_action?(action, path.blank?) + "#{prefix}(.:format)" else - case @scope[:scope_level] - when :collection, :new - "#{@scope[:path]}/#{action_path(action, path)}(.:format)" - else - if parent_resource.shallow? - "#{@scope[:shallow_path]}/#{parent_resource.path}/:id/#{action_path(action, path)}(.:format)" - else - "#{@scope[:path]}/#{action_path(action, path)}(.:format)" - end - end + "#{prefix}/#{action_path(action, path)}(.:format)" end end def path_for_custom_action - case @scope[:scope_level] - when :collection, :new - @scope[:path] + if shallow_scoping? + "#{@scope[:shallow_path]}/#{parent_resource.path}/:id" else - if parent_resource.shallow? - "#{@scope[:shallow_path]}/#{parent_resource.path}/:id" - else - @scope[:path] - end + @scope[:path] end end @@ -917,50 +914,40 @@ module ActionDispatch path || @scope[:path_names][name.to_sym] || name.to_s end - def options_for_action(action, options) - options.reverse_merge( - :to => action, - :as => name_for_action(action) - ) - end - - def name_for_action(action) - name_prefix = @scope[:as].blank? ? "" : "#{@scope[:as]}_" - shallow_prefix = @scope[:shallow_prefix].blank? ? "" : "#{@scope[:shallow_prefix]}_" - - case action - when :index, :create - "#{name_prefix}#{parent_resource.collection_name}" - when :show, :update, :destroy - if parent_resource.shallow? - "#{shallow_prefix}#{parent_resource.member_name}" - else - "#{name_prefix}#{parent_resource.member_name}" - end - when :edit - if parent_resource.shallow? - "edit_#{shallow_prefix}#{parent_resource.member_name}" - else - "edit_#{name_prefix}#{parent_resource.member_name}" - end - when :new - "new_#{name_prefix}#{parent_resource.member_name}" - else - case @scope[:scope_level] - when :collection - "#{action}_#{name_prefix}#{parent_resource.collection_name}" - when :new - "#{action}_new_#{name_prefix}#{parent_resource.member_name}" - else - if parent_resource.shallow? - "#{action}_#{shallow_prefix}#{parent_resource.member_name}" - else - "#{action}_#{name_prefix}#{parent_resource.member_name}" - end - end + def prefix_name_for_action(action, as) + if as.present? + "#{as}_" + elsif as + "" + elsif !canonical_action?(action, @scope[:scope_level]) + "#{action}_" end end + def name_for_action(action, as=nil) + prefix = prefix_name_for_action(action, as) + name_prefix = @scope[:as] + + if parent_resource + collection_name = parent_resource.collection_name + member_name = parent_resource.member_name + name_prefix = "#{name_prefix}_" if name_prefix.present? + end + + case @scope[:scope_level] + when :collection + "#{prefix}#{name_prefix}#{collection_name}" + when :new + "#{prefix}new_#{name_prefix}#{member_name}" + else + if shallow_scoping? + shallow_prefix = "#{@scope[:shallow_prefix]}_" if @scope[:shallow_prefix].present? + "#{prefix}#{shallow_prefix}#{member_name}" + else + "#{prefix}#{name_prefix}#{member_name}" + end + end + end end include Base diff --git a/actionpack/lib/action_dispatch/routing/route.rb b/actionpack/lib/action_dispatch/routing/route.rb index 6f37eadd6b9..aefebf8f80e 100644 --- a/actionpack/lib/action_dispatch/routing/route.rb +++ b/actionpack/lib/action_dispatch/routing/route.rb @@ -2,9 +2,10 @@ module ActionDispatch module Routing class Route #:nodoc: attr_reader :app, :conditions, :defaults, :name - attr_reader :path, :requirements + attr_reader :path, :requirements, :set - def initialize(app, conditions, requirements, defaults, name, anchor) + def initialize(set, app, conditions, requirements, defaults, name, anchor) + @set = set @app = app @defaults = defaults @name = name @@ -24,6 +25,9 @@ module ActionDispatch h[k] = Rack::Mount::RegexpWithNamedGroups.new(v) h } + + @conditions.delete_if{ |k,v| k != :path_info && !valid_condition?(k) } + @requirements.delete_if{ |k,v| !valid_condition?(k) } end def verb @@ -50,6 +54,11 @@ module ActionDispatch "%-6s %-40s %s" % [(verb || :any).to_s.upcase, path, requirements.inspect] end end + + private + def valid_condition?(method) + segment_keys.include?(method) || set.valid_conditions.include?(method) + end end end end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 7be79d32005..5ecad6bc042 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -1,8 +1,10 @@ -require 'rack/mount' require 'forwardable' require 'active_support/core_ext/object/to_query' require 'action_dispatch/routing/deprecated_mapper' +$: << File.expand_path('../../vendor/rack-mount-0.6.6.pre', __FILE__) +require 'rack/mount' + module ActionDispatch module Routing class RouteSet #:nodoc: @@ -187,7 +189,7 @@ module ActionDispatch attr_accessor :set, :routes, :named_routes attr_accessor :disable_clear_and_finalize, :resources_path_names - attr_accessor :default_url_options, :request_class + attr_accessor :default_url_options, :request_class, :valid_conditions def self.default_resources_path_names { :new => 'new', :edit => 'edit' } @@ -199,7 +201,11 @@ module ActionDispatch self.resources_path_names = self.class.default_resources_path_names.dup self.controller_namespaces = Set.new self.default_url_options = {} + self.request_class = request_class + self.valid_conditions = request_class.public_instance_methods.map { |m| m.to_sym } + self.valid_conditions.delete(:id) + self.valid_conditions.push(:controller, :action) @disable_clear_and_finalize = false clear! @@ -277,7 +283,7 @@ module ActionDispatch end def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true) - route = Route.new(app, conditions, requirements, defaults, name, anchor) + route = Route.new(self, app, conditions, requirements, defaults, name, anchor) @set.add_route(*route) named_routes[name] = route if name routes << route diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount.rb new file mode 100644 index 00000000000..9fbf7077248 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount.rb @@ -0,0 +1,32 @@ +require 'rack' + +module Rack #:nodoc: + # A stackable dynamic tree based Rack router. + # + # Rack::Mount supports Rack's Cascade style of trying several routes until + # it finds one that is not a 404. This allows multiple routes to be nested + # or stacked on top of each other. Since the application endpoint can + # trigger the router to continue matching, middleware can be used to add + # arbitrary conditions to any route. This allows you to route based on + # other request attributes, session information, or even data dynamically + # pulled from a database. + module Mount + autoload :CodeGeneration, 'rack/mount/code_generation' + autoload :GeneratableRegexp, 'rack/mount/generatable_regexp' + autoload :Multimap, 'rack/mount/multimap' + autoload :Prefix, 'rack/mount/prefix' + autoload :RegexpWithNamedGroups, 'rack/mount/regexp_with_named_groups' + autoload :Route, 'rack/mount/route' + autoload :RouteSet, 'rack/mount/route_set' + autoload :RoutingError, 'rack/mount/route_set' + autoload :Strexp, 'rack/mount/strexp' + autoload :Utils, 'rack/mount/utils' + autoload :Version, 'rack/mount/version' + + module Analysis #:nodoc: + autoload :Frequency, 'rack/mount/analysis/frequency' + autoload :Histogram, 'rack/mount/analysis/histogram' + autoload :Splitting, 'rack/mount/analysis/splitting' + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/frequency.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/frequency.rb new file mode 100644 index 00000000000..671258f807f --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/frequency.rb @@ -0,0 +1,60 @@ +require 'rack/mount/utils' + +module Rack::Mount + module Analysis + class Frequency #:nodoc: + def initialize(*keys) + clear + keys.each { |key| self << key } + end + + def clear + @raw_keys = [] + @key_frequency = Analysis::Histogram.new + self + end + + def <<(key) + raise ArgumentError unless key.is_a?(Hash) + @raw_keys << key + nil + end + + def possible_keys + @possible_keys ||= begin + @raw_keys.map do |key| + key.inject({}) { |requirements, (method, requirement)| + process_key(requirements, method, requirement) + requirements + } + end + end + end + + def process_key(requirements, method, requirement) + if requirement.is_a?(Regexp) + expression = Utils.parse_regexp(requirement) + + if expression.is_a?(Regin::Expression) && expression.anchored_to_line? + expression = Regin::Expression.new(expression.reject { |e| e.is_a?(Regin::Anchor) }) + return requirements[method] = expression.to_s if expression.literal? + end + end + + requirements[method] = requirement + end + + def report + @report ||= begin + possible_keys.each { |keys| keys.each_pair { |key, _| @key_frequency << key } } + return [] if @key_frequency.count <= 1 + @key_frequency.keys_in_upper_quartile + end + end + + def expire! + @possible_keys = @report = nil + end + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/histogram.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/histogram.rb new file mode 100644 index 00000000000..20aaa132f98 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/histogram.rb @@ -0,0 +1,74 @@ +module Rack::Mount + module Analysis + class Histogram < Hash #:nodoc: + attr_reader :count + + def initialize + @count = 0 + super(0) + expire_caches! + end + + def <<(value) + @count += 1 + self[value] += 1 if value + expire_caches! + self + end + + def sorted_by_frequency + sort_by { |_, value| value }.reverse! + end + + def max + @max ||= values.max || 0 + end + + def min + @min ||= values.min || 0 + end + + def mean + @mean ||= calculate_mean + end + + def standard_deviation + @standard_deviation ||= calculate_standard_deviation + end + + def upper_quartile_limit + @upper_quartile_limit ||= calculate_upper_quartile_limit + end + + def keys_in_upper_quartile + @keys_in_upper_quartile ||= compute_keys_in_upper_quartile + end + + private + def calculate_mean + count / size + end + + def calculate_variance + values.inject(0) { |sum, e| sum + (e - mean) ** 2 } / count.to_f + end + + def calculate_standard_deviation + Math.sqrt(calculate_variance) + end + + def calculate_upper_quartile_limit + mean + standard_deviation + end + + def compute_keys_in_upper_quartile + sorted_by_frequency.select { |_, value| value >= upper_quartile_limit }.map! { |key, _| key } + end + + def expire_caches! + @max = @min = @mean = @standard_deviation = nil + @keys_in_upper_quartile = nil + end + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/splitting.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/splitting.rb new file mode 100644 index 00000000000..8a8c5513021 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/splitting.rb @@ -0,0 +1,159 @@ +require 'rack/mount/utils' + +module Rack::Mount + module Analysis + class Splitting < Frequency + NULL = "\0".freeze + + class Key < Struct.new(:method, :index, :separators) + def self.split(value, separator_pattern) + keys = value.split(separator_pattern) + keys.shift if keys[0] == '' + keys << NULL + keys + end + + def call(cache, obj) + (cache[method] ||= self.class.split(obj.send(method), separators))[index] + end + + def call_source(cache, obj) + "(#{cache}[:#{method}] ||= Analysis::Splitting::Key.split(#{obj}.#{method}, #{separators.inspect}))[#{index}]" + end + + def inspect + "#{method}[#{index}]" + end + end + + def clear + @boundaries = {} + super + end + + def <<(key) + super + key.each_pair do |k, v| + analyze_capture_boundaries(v, @boundaries[k] ||= Histogram.new) + end + end + + def separators(key) + (@boundaries[key].keys_in_upper_quartile + ['/']).uniq + end + + def process_key(requirements, method, requirement) + separators = separators(method) + if requirement.is_a?(Regexp) && separators.any? + generate_split_keys(requirement, separators).each_with_index do |value, index| + requirements[Key.new(method, index, Regexp.union(*separators))] = value + end + else + super + end + end + + private + def analyze_capture_boundaries(regexp, boundaries) #:nodoc: + return boundaries unless regexp.is_a?(Regexp) + + parts = Utils.parse_regexp(regexp) + parts.each_with_index do |part, index| + if part.is_a?(Regin::Group) + if index > 0 + previous = parts[index-1] + if previous.is_a?(Regin::Character) && previous.literal? + boundaries << previous.to_s + end + end + + if inside = part.expression[0] + if inside.is_a?(Regin::Character) && inside.literal? + boundaries << inside.to_s + end + end + + if index < parts.length + following = parts[index+1] + if following.is_a?(Regin::Character) && following.literal? + boundaries << following.to_s + end + end + end + end + + boundaries + end + + def generate_split_keys(regexp, separators) #:nodoc: + segments = [] + buf = nil + parts = Utils.parse_regexp(regexp) + parts.each_with_index do |part, index| + case part + when Regin::Anchor + if part.value == '$' || part.value == '\Z' + segments << join_buffer(buf, regexp) if buf + segments << NULL + buf = nil + break + end + when Regin::CharacterClass + break if separators.any? { |s| part.include?(s) } + buf = nil + segments << part.to_regexp(true) + when Regin::Character + if separators.any? { |s| part.include?(s) } + segments << join_buffer(buf, regexp) if buf + peek = parts[index+1] + if peek.is_a?(Regin::Character) && separators.include?(peek.value) + segments << '' + end + buf = nil + else + buf ||= Regin::Expression.new([]) + buf += [part] + end + when Regin::Group + if part.quantifier == '?' + value = part.expression.first + if separators.any? { |s| value.include?(s) } + segments << join_buffer(buf, regexp) if buf + buf = nil + end + break + elsif part.quantifier == nil + break if separators.any? { |s| part.include?(s) } + buf = nil + segments << part.to_regexp(true) + else + break + end + else + break + end + + if index + 1 == parts.size + segments << join_buffer(buf, regexp) if buf + buf = nil + break + end + end + + while segments.length > 0 && (segments.last.nil? || segments.last == '') + segments.pop + end + + segments + end + + def join_buffer(parts, regexp) + if parts.literal? + parts.to_s + else + parts.to_regexp(true) + end + end + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/code_generation.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/code_generation.rb new file mode 100644 index 00000000000..903c79fdc61 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/code_generation.rb @@ -0,0 +1,113 @@ +module Rack::Mount + module CodeGeneration #:nodoc: + def _expired_recognize(env) #:nodoc: + raise 'route set not finalized' + end + + def rehash + super + optimize_recognize! + end + + private + def expire! + if @optimized_recognize_defined + remove_metaclass_method :recognize + + class << self + alias_method :recognize, :_expired_recognize + end + + @optimized_recognize_defined = false + end + + super + end + + def optimize_container_iterator(container) + body = [] + + container.each_with_index { |route, i| + body << "route = self[#{i}]" + body << 'matches = {}' + body << 'params = route.defaults.dup' + + conditions = [] + route.conditions.each do |method, condition| + b = [] + if condition.is_a?(Regexp) + b << "if m = obj.#{method}.match(#{condition.inspect})" + b << "matches[:#{method}] = m" + if (named_captures = route.named_captures[method]) && named_captures.any? + b << 'captures = m.captures' + b << 'p = nil' + b << named_captures.map { |k, j| "params[#{k.inspect}] = p if p = captures[#{j}]" }.join('; ') + end + else + b << "if m = obj.#{method} == route.conditions[:#{method}]" + end + b << 'true' + b << 'end' + conditions << "(#{b.join('; ')})" + end + + body << <<-RUBY + if #{conditions.join(' && ')} + yield route, matches, params + end + RUBY + } + + container.instance_eval(<<-RUBY, __FILE__, __LINE__) + def optimized_each(obj) + #{body.join("\n")} + nil + end + RUBY + end + + def optimize_recognize! + keys = @recognition_keys.map { |key| + if key.respond_to?(:call_source) + key.call_source(:cache, :obj) + else + "obj.#{key}" + end + }.join(', ') + + @optimized_recognize_defined = true + + remove_metaclass_method :recognize + + instance_eval(<<-RUBY, __FILE__, __LINE__) + def recognize(obj) + cache = {} + container = @recognition_graph[#{keys}] + optimize_container_iterator(container) unless container.respond_to?(:optimized_each) + + if block_given? + container.optimized_each(obj) do |route, matches, params| + yield route, matches, params + end + else + container.optimized_each(obj) do |route, matches, params| + return route, matches, params + end + end + + nil + end + RUBY + end + + # method_defined? can't distinguish between instance + # and meta methods. So we have to rescue if the method + # has not been defined in the metaclass yet. + def remove_metaclass_method(symbol) + metaclass = class << self; self; end + metaclass.send(:remove_method, symbol) + rescue NameError => e + nil + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/generatable_regexp.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/generatable_regexp.rb new file mode 100644 index 00000000000..47bbab3784e --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/generatable_regexp.rb @@ -0,0 +1,210 @@ +require 'rack/mount/utils' + +module Rack::Mount + class GeneratableRegexp < Regexp #:nodoc: + class DynamicSegment #:nodoc: + attr_reader :name, :requirement + + def initialize(name, requirement) + @name, @requirement = name.to_sym, requirement + freeze + end + + def ==(obj) + @name == obj.name && @requirement == obj.requirement + end + + def =~(str) + @requirement =~ str + end + + def to_hash + { @name => @requirement } + end + + def inspect + "/(?<#{@name}>#{@requirement.source})/" + end + end + + module InstanceMethods + def self.extended(obj) + obj.segments + end + + def defaults=(defaults) + @required_captures = nil + @required_params = nil + @required_defaults = nil + @defaults = defaults + end + + def defaults + @defaults ||= {} + end + + def generatable? + segments.any? + end + + def generate(params = {}, recall = {}, options = {}) + return nil unless generatable? + + merged = recall.merge(params) + return nil unless required_params.all? { |p| merged.include?(p) } + return nil unless required_defaults.all? { |k, v| merged[k] == v } + + generate_from_segments(segments, params, merged, options) + end + + def segments + @segments ||= begin + defaults + segments = [] + catch(:halt) do + expression = Utils.parse_regexp(self) + segments = parse_segments(expression) + end + segments + end + end + + def captures + segments.flatten.find_all { |s| s.is_a?(DynamicSegment) } + end + + def required_captures + @required_captures ||= segments.find_all { |s| + s.is_a?(DynamicSegment) && !@defaults.include?(s.name) + }.freeze + end + + def required_params + @required_params ||= required_captures.map { |s| s.name }.freeze + end + + def required_defaults + @required_defaults ||= begin + required_defaults = @defaults.dup + captures.inject({}) { |h, s| h.merge!(s.to_hash) }.keys.each { |name| + required_defaults.delete(name) + } + required_defaults + end + end + + def freeze + segments + captures + required_captures + required_params + required_defaults + super + end + + private + def parse_segments(segments) + s = [] + segments.each_with_index do |part, index| + case part + when Regin::Anchor + # ignore + when Regin::Character + throw :halt unless part.literal? + + if s.last.is_a?(String) + s.last << part.value.dup + else + s << part.value.dup + end + when Regin::Group + if part.name + s << DynamicSegment.new(part.name, part.expression.to_regexp(true)) + else + s << parse_segments(part.expression) + end + when Regin::Expression + return parse_segments(part) + else + throw :halt + end + end + + s + end + + EMPTY_STRING = ''.freeze + + def generate_from_segments(segments, params, merged, options, optional = false) + if optional + return EMPTY_STRING if segments.all? { |s| s.is_a?(String) } + return EMPTY_STRING unless segments.flatten.any? { |s| + params.has_key?(s.name) if s.is_a?(DynamicSegment) + } + return EMPTY_STRING if segments.any? { |segment| + if segment.is_a?(DynamicSegment) + value = merged[segment.name] || @defaults[segment.name] + value = parameterize(segment.name, value, options) + + merged_value = parameterize(segment.name, merged[segment.name], options) + default_value = parameterize(segment.name, @defaults[segment.name], options) + + if value.nil? || segment !~ value + true + elsif merged_value == default_value + # Nasty control flow + return :clear_remaining_segments + else + false + end + end + } + end + + generated = segments.map do |segment| + case segment + when String + segment + when DynamicSegment + value = params[segment.name] || merged[segment.name] || @defaults[segment.name] + value = parameterize(segment.name, value, options) + if value && segment =~ value.to_s + value + else + return + end + when Array + value = generate_from_segments(segment, params, merged, options, true) + if value == :clear_remaining_segments + segment.each { |s| params.delete(s.name) if s.is_a?(DynamicSegment) } + EMPTY_STRING + elsif value.nil? + EMPTY_STRING + else + value + end + end + end + + # Delete any used items from the params + segments.each { |s| params.delete(s.name) if s.is_a?(DynamicSegment) } + + generated.join + end + + def parameterize(name, value, options) + if block = options[:parameterize] + block.call(name, value) + else + value + end + end + end + include InstanceMethods + + def initialize(regexp) + super + segments + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/multimap.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/multimap.rb new file mode 100644 index 00000000000..0f8eaaec67d --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/multimap.rb @@ -0,0 +1,53 @@ +begin + require 'nested_multimap' +rescue LoadError + $: << File.expand_path(File.join(File.dirname(__FILE__), 'vendor/multimap')) + require 'nested_multimap' +end + +module Rack::Mount + class Multimap < NestedMultimap #:nodoc: + def store(*args) + keys = args.dup + value = keys.pop + key = keys.shift + + raise ArgumentError, 'wrong number of arguments (1 for 2)' unless value + + unless key.respond_to?(:=~) + raise ArgumentError, "unsupported key: #{args.first.inspect}" + end + + if key.is_a?(Regexp) + if keys.empty? + @hash.each_pair { |k, l| l << value if k =~ key } + self.default << value + else + @hash.each_pair { |k, _| + if k =~ key + args[0] = k + super(*args) + end + } + + self.default = self.class.new(default) unless default.is_a?(self.class) + default[*keys.dup] = value + end + else + super(*args) + end + end + alias_method :[]=, :store + + undef :index, :invert + + def height + containers_with_default.max { |a, b| a.length <=> b.length }.length + end + + def average_height + lengths = containers_with_default.map { |e| e.length } + lengths.inject(0) { |sum, len| sum += len }.to_f / lengths.size + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/prefix.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/prefix.rb new file mode 100644 index 00000000000..58892e4c4f5 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/prefix.rb @@ -0,0 +1,36 @@ +require 'rack/mount/utils' + +module Rack::Mount + class Prefix #:nodoc: + EMPTY_STRING = ''.freeze + PATH_INFO = 'PATH_INFO'.freeze + SCRIPT_NAME = 'SCRIPT_NAME'.freeze + SLASH = '/'.freeze + + KEY = 'rack.mount.prefix'.freeze + + def initialize(app, prefix = nil) + @app, @prefix = app, prefix.freeze + freeze + end + + def call(env) + if prefix = env[KEY] || @prefix + old_path_info = env[PATH_INFO].dup + old_script_name = env[SCRIPT_NAME].dup + + begin + env[PATH_INFO] = Utils.normalize_path(env[PATH_INFO].sub(prefix, EMPTY_STRING)) + env[PATH_INFO] = EMPTY_STRING if env[PATH_INFO] == SLASH + env[SCRIPT_NAME] = Utils.normalize_path(env[SCRIPT_NAME].to_s + prefix) + @app.call(env) + ensure + env[PATH_INFO] = old_path_info + env[SCRIPT_NAME] = old_script_name + end + else + @app.call(env) + end + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/regexp_with_named_groups.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/regexp_with_named_groups.rb new file mode 100644 index 00000000000..c11292b2a26 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/regexp_with_named_groups.rb @@ -0,0 +1,69 @@ +module Rack::Mount + if Regin.regexp_supports_named_captures? + RegexpWithNamedGroups = Regexp + else + require 'strscan' + + # A wrapper that adds shim named capture support to older + # versions of Ruby. + # + # Because the named capture syntax causes a parse error, an + # alternate syntax is used to indicate named captures. + # + # Ruby 1.9+ named capture syntax: + # + # /(?[a-z]+)/ + # + # Ruby 1.8 shim syntax: + # + # /(?:[a-z]+)/ + class RegexpWithNamedGroups < Regexp + def self.new(regexp) #:nodoc: + if regexp.is_a?(RegexpWithNamedGroups) + regexp + else + super + end + end + + # Wraps Regexp with named capture support. + def initialize(regexp) + regexp = Regexp.compile(regexp) unless regexp.is_a?(Regexp) + source, options = regexp.source, regexp.options + @names, scanner = [], StringScanner.new(source) + + while scanner.skip_until(/\(/) + if scanner.scan(/\?:<([^>]+)>/) + @names << scanner[1] + elsif scanner.scan(/\?(i?m?x?\-?i?m?x?)?:/) + # ignore noncapture + else + @names << nil + end + end + source.gsub!(/\?:<([^>]+)>/, '') + + @names = [] unless @names.any? + @names.freeze + + super(source, options) + end + + def names + @names.dup + end + + def named_captures + named_captures = {} + names.each_with_index { |n, i| + named_captures[n] = [i+1] if n + } + named_captures + end + + def eql?(other) + super && @names.eql?(other.names) + end + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route.rb new file mode 100644 index 00000000000..680c40f1472 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route.rb @@ -0,0 +1,130 @@ +require 'rack/mount/generatable_regexp' +require 'rack/mount/regexp_with_named_groups' +require 'rack/mount/utils' + +module Rack::Mount + # Route is an internal class used to wrap a single route attributes. + # + # Plugins should not depend on any method on this class or instantiate + # new Route objects. Instead use the factory method, RouteSet#add_route + # to create new routes and add them to the set. + class Route + # Valid rack application to call if conditions are met + attr_reader :app + + # A hash of conditions to match against. Conditions may be expressed + # as strings or regexps to match against. + attr_reader :conditions + + # A hash of values that always gets merged into the parameters hash + attr_reader :defaults + + # Symbol identifier for the route used with named route generations + attr_reader :name + + attr_reader :named_captures + + def initialize(app, conditions, defaults, name) + unless app.respond_to?(:call) + raise ArgumentError, 'app must be a valid rack application' \ + ' and respond to call' + end + @app = app + + @name = name ? name.to_sym : nil + @defaults = (defaults || {}).freeze + + @conditions = {} + + conditions.each do |method, pattern| + next unless method && pattern + + pattern = Regexp.compile("\\A#{Regexp.escape(pattern)}\\Z") if pattern.is_a?(String) + + if pattern.is_a?(Regexp) + pattern = Utils.normalize_extended_expression(pattern) + pattern = RegexpWithNamedGroups.new(pattern) + pattern.extend(GeneratableRegexp::InstanceMethods) + pattern.defaults = @defaults + end + + @conditions[method] = pattern.freeze + end + + @named_captures = {} + @conditions.map { |method, condition| + next unless condition.respond_to?(:named_captures) + @named_captures[method] = condition.named_captures.inject({}) { |named_captures, (k, v)| + named_captures[k.to_sym] = v.last - 1 + named_captures + }.freeze + } + @named_captures.freeze + + @has_significant_params = @conditions.any? { |method, condition| + (condition.respond_to?(:required_params) && condition.required_params.any?) || + (condition.respond_to?(:required_defaults) && condition.required_defaults.any?) + } + + if @conditions.has_key?(:path_info) && + !Utils.regexp_anchored?(@conditions[:path_info]) + @prefix = true + @app = Prefix.new(@app) + else + @prefix = false + end + + @conditions.freeze + end + + def prefix? + @prefix + end + + + def generation_keys + @conditions.inject({}) { |keys, (method, condition)| + if condition.respond_to?(:required_defaults) + keys.merge!(condition.required_defaults) + else + keys + end + } + end + + def significant_params? + @has_significant_params + end + + def generate(method, params = {}, recall = {}, options = {}) + if method.nil? + result = @conditions.inject({}) { |h, (m, condition)| + if condition.respond_to?(:generate) + h[m] = condition.generate(params, recall, options) + end + h + } + return nil if result.values.compact.empty? + else + if condition = @conditions[method] + if condition.respond_to?(:generate) + result = condition.generate(params, recall, options) + end + end + end + + if result + @defaults.each do |key, value| + params.delete(key) if params[key] == value + end + end + + result + end + + + def inspect #:nodoc: + "#<#{self.class.name} @app=#{@app.inspect} @conditions=#{@conditions.inspect} @defaults=#{@defaults.inspect} @name=#{@name.inspect}>" + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb new file mode 100644 index 00000000000..0e5a65a640e --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb @@ -0,0 +1,409 @@ +require 'rack/mount/multimap' +require 'rack/mount/route' +require 'rack/mount/utils' + +module Rack::Mount + class RoutingError < StandardError; end + + class RouteSet + # Initialize a new RouteSet without optimizations + def self.new_without_optimizations(options = {}, &block) + new(options.merge(:_optimize => false), &block) + end + + # Basic RouteSet initializer. + # + # If a block is given, the set is yielded and finalized. + # + # See other aspects for other valid options: + # - Generation::RouteSet.new + # - Recognition::RouteSet.new + def initialize(options = {}, &block) + @parameters_key = options.delete(:parameters_key) || 'rack.routing_args' + @parameters_key.freeze + + @named_routes = {} + + @recognition_key_analyzer = Analysis::Splitting.new + @generation_key_analyzer = Analysis::Frequency.new + + @request_class = options.delete(:request_class) || Rack::Request + @valid_conditions = @request_class.public_instance_methods.map! { |m| m.to_sym } + + extend CodeGeneration unless options[:_optimize] == false + @optimized_recognize_defined = false + + @routes = [] + expire! + + if block_given? + yield self + rehash + end + end + + # Builder method to add a route to the set + # + # app:: A valid Rack app to call if the conditions are met. + # conditions:: A hash of conditions to match against. + # Conditions may be expressed as strings or + # regexps to match against. + # defaults:: A hash of values that always gets merged in + # name:: Symbol identifier for the route used with named + # route generations + def add_route(app, conditions = {}, defaults = {}, name = nil) + unless conditions.is_a?(Hash) + raise ArgumentError, 'conditions must be a Hash' + end + + unless conditions.all? { |method, pattern| + @valid_conditions.include?(method) + } + raise ArgumentError, 'conditions may only include ' + + @valid_conditions.inspect + end + + route = Route.new(app, conditions, defaults, name) + @routes << route + + @recognition_key_analyzer << route.conditions + + @named_routes[route.name] = route if route.name + @generation_key_analyzer << route.generation_keys + + expire! + route + end + + def recognize(obj) + raise 'route set not finalized' unless @recognition_graph + + cache = {} + keys = @recognition_keys.map { |key| + if key.respond_to?(:call) + key.call(cache, obj) + else + obj.send(key) + end + } + + @recognition_graph[*keys].each do |route| + matches = {} + params = route.defaults.dup + + if route.conditions.all? { |method, condition| + value = obj.send(method) + if condition.is_a?(Regexp) && (m = value.match(condition)) + matches[method] = m + captures = m.captures + route.named_captures[method].each do |k, i| + if v = captures[i] + params[k] = v + end + end + true + elsif value == condition + true + else + false + end + } + if block_given? + yield route, matches, params + else + return route, matches, params + end + end + end + + nil + end + + X_CASCADE = 'X-Cascade'.freeze + PASS = 'pass'.freeze + PATH_INFO = 'PATH_INFO'.freeze + + # Rack compatible recognition and dispatching method. Routes are + # tried until one returns a non-catch status code. If no routes + # match, the catch status code is returned. + # + # This method can only be invoked after the RouteSet has been + # finalized. + def call(env) + raise 'route set not finalized' unless @recognition_graph + + env[PATH_INFO] = Utils.normalize_path(env[PATH_INFO]) + + request = nil + req = @request_class.new(env) + recognize(req) do |route, matches, params| + # TODO: We only want to unescape params from uri related methods + params.each { |k, v| params[k] = Utils.unescape_uri(v) if v.is_a?(String) } + + if route.prefix? + env[Prefix::KEY] = matches[:path_info].to_s + end + + env[@parameters_key] = params + result = route.app.call(env) + return result unless result[1][X_CASCADE] == PASS + end + + request || [404, {'Content-Type' => 'text/html', 'X-Cascade' => 'pass'}, ['Not Found']] + end + + # Generates a url from Rack env and identifiers or significant keys. + # + # To generate a url by named route, pass the name in as a +Symbol+. + # url(env, :dashboard) # => "/dashboard" + # + # Additional parameters can be passed in as a hash + # url(env, :people, :id => "1") # => "/people/1" + # + # If no name route is given, it will fall back to a slower + # generation search. + # url(env, :controller => "people", :action => "show", :id => "1") + # # => "/people/1" + def url(env, *args) + named_route, params = nil, {} + + case args.length + when 2 + named_route, params = args[0], args[1].dup + when 1 + if args[0].is_a?(Hash) + params = args[0].dup + else + named_route = args[0] + end + else + raise ArgumentError + end + + only_path = params.delete(:only_path) + recall = env[@parameters_key] || {} + + unless result = generate(:all, named_route, params, recall, + :parameterize => lambda { |name, param| Utils.escape_uri(param) }) + return + end + + parts, params = result + return unless parts + + params.each do |k, v| + if v + params[k] = v + else + params.delete(k) + end + end + + req = stubbed_request_class.new(env) + req._stubbed_values = parts.merge(:query_string => Utils.build_nested_query(params)) + only_path ? req.fullpath : req.url + end + + def generate(method, *args) #:nodoc: + raise 'route set not finalized' unless @generation_graph + + method = nil if method == :all + named_route, params, recall, options = extract_params!(*args) + merged = recall.merge(params) + route = nil + + if named_route + if route = @named_routes[named_route.to_sym] + recall = route.defaults.merge(recall) + url = route.generate(method, params, recall, options) + [url, params] + else + raise RoutingError, "#{named_route} failed to generate from #{params.inspect}" + end + else + keys = @generation_keys.map { |key| + if k = merged[key] + k.to_s + else + nil + end + } + @generation_graph[*keys].each do |r| + next unless r.significant_params? + if url = r.generate(method, params, recall, options) + return [url, params] + end + end + + raise RoutingError, "No route matches #{params.inspect}" + end + end + + # Number of routes in the set + def length + @routes.length + end + + def rehash #:nodoc: + @recognition_keys = build_recognition_keys + @recognition_graph = build_recognition_graph + @generation_keys = build_generation_keys + @generation_graph = build_generation_graph + end + + # Finalizes the set and builds optimized data structures. You *must* + # freeze the set before you can use call and url. + # So remember to call freeze after you are done adding routes. + def freeze + unless frozen? + rehash + + @recognition_key_analyzer = nil + @generation_key_analyzer = nil + @valid_conditions = nil + + @routes.each { |route| route.freeze } + @routes.freeze + end + + super + end + + def marshal_dump #:nodoc: + hash = {} + + instance_variables_to_serialize.each do |ivar| + hash[ivar] = instance_variable_get(ivar) + end + + if graph = hash[:@recognition_graph] + hash[:@recognition_graph] = graph.dup + end + + hash + end + + def marshal_load(hash) #:nodoc: + hash.each do |ivar, value| + instance_variable_set(ivar, value) + end + end + + protected + def recognition_stats + { :keys => @recognition_keys, + :keys_size => @recognition_keys.size, + :graph_size => @recognition_graph.size, + :graph_height => @recognition_graph.height, + :graph_average_height => @recognition_graph.average_height } + end + + private + def expire! #:nodoc: + @recognition_keys = @recognition_graph = nil + @recognition_key_analyzer.expire! + + @generation_keys = @generation_graph = nil + @generation_key_analyzer.expire! + end + + def instance_variables_to_serialize + instance_variables.map { |ivar| ivar.to_sym } - [:@stubbed_request_class, :@optimized_recognize_defined] + end + + # An internal helper method for constructing a nested set from + # the linear route set. + # + # build_nested_route_set([:request_method, :path_info]) { |route, method| + # route.send(method) + # } + def build_nested_route_set(keys, &block) + graph = Multimap.new + @routes.each_with_index do |route, index| + catch(:skip) do + k = keys.map { |key| block.call(key, index) } + Utils.pop_trailing_nils!(k) + k.map! { |key| key || /.+/ } + graph[*k] = route + end + end + graph + end + + def build_recognition_graph + build_nested_route_set(@recognition_keys) { |k, i| + @recognition_key_analyzer.possible_keys[i][k] + } + end + + def build_recognition_keys + @recognition_key_analyzer.report + end + + def build_generation_graph + build_nested_route_set(@generation_keys) { |k, i| + throw :skip unless @routes[i].significant_params? + + if k = @generation_key_analyzer.possible_keys[i][k] + k.to_s + else + nil + end + } + end + + def build_generation_keys + @generation_key_analyzer.report + end + + def extract_params!(*args) + case args.length + when 4 + named_route, params, recall, options = args + when 3 + if args[0].is_a?(Hash) + params, recall, options = args + else + named_route, params, recall = args + end + when 2 + if args[0].is_a?(Hash) + params, recall = args + else + named_route, params = args + end + when 1 + if args[0].is_a?(Hash) + params = args[0] + else + named_route = args[0] + end + else + raise ArgumentError + end + + named_route ||= nil + params ||= {} + recall ||= {} + options ||= {} + + [named_route, params.dup, recall.dup, options.dup] + end + + def stubbed_request_class + @stubbed_request_class ||= begin + klass = Class.new(@request_class) + klass.public_instance_methods.each do |method| + next if method =~ /^__|object_id/ + klass.class_eval <<-RUBY + def #{method}(*args, &block) + @_stubbed_values[:#{method}] || super + end + RUBY + end + klass.class_eval { attr_accessor :_stubbed_values } + klass + end + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp.rb new file mode 100644 index 00000000000..d0d87970086 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp.rb @@ -0,0 +1,68 @@ +require 'rack/mount/strexp/parser' + +module Rack::Mount + class Strexp + class << self + # Parses segmented string expression and converts it into a Regexp + # + # Strexp.compile('foo') + # # => %r{\Afoo\Z} + # + # Strexp.compile('foo/:bar', {}, ['/']) + # # => %r{\Afoo/(?[^/]+)\Z} + # + # Strexp.compile(':foo.example.com') + # # => %r{\A(?.+)\.example\.com\Z} + # + # Strexp.compile('foo/:bar', {:bar => /[a-z]+/}, ['/']) + # # => %r{\Afoo/(?[a-z]+)\Z} + # + # Strexp.compile('foo(.:extension)') + # # => %r{\Afoo(\.(?.+))?\Z} + # + # Strexp.compile('src/*files') + # # => %r{\Asrc/(?.+)\Z} + def compile(str, requirements = {}, separators = [], anchor = true) + return Regexp.compile(str) if str.is_a?(Regexp) + + requirements = requirements ? requirements.dup : {} + normalize_requirements!(requirements, separators) + + parser = StrexpParser.new + parser.anchor = anchor + parser.requirements = requirements + + begin + re = parser.scan_str(str) + rescue Racc::ParseError => e + raise RegexpError, e.message + end + + Regexp.compile(re) + end + alias_method :new, :compile + + private + def normalize_requirements!(requirements, separators) + requirements.each do |key, value| + if value.is_a?(Regexp) + if regexp_has_modifiers?(value) + requirements[key] = value + else + requirements[key] = value.source + end + else + requirements[key] = Regexp.escape(value) + end + end + requirements.default ||= separators.any? ? + "[^#{separators.join}]+" : '.+' + requirements + end + + def regexp_has_modifiers?(regexp) + regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0 + end + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.rb new file mode 100644 index 00000000000..cfe05afc61d --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.rb @@ -0,0 +1,160 @@ +# +# DO NOT MODIFY!!!! +# This file is automatically generated by Racc 1.4.6 +# from Racc grammer file "". +# + +require 'racc/parser.rb' + +require 'rack/mount/utils' +require 'rack/mount/strexp/tokenizer' + +module Rack + module Mount + class StrexpParser < Racc::Parser + + +if Regin.regexp_supports_named_captures? + REGEXP_NAMED_CAPTURE = '(?<%s>%s)'.freeze +else + REGEXP_NAMED_CAPTURE = '(?:<%s>%s)'.freeze +end + +attr_accessor :anchor, :requirements +##### State transition tables begin ### + +racc_action_table = [ + 1, 2, 3, 9, 4, 1, 2, 3, 12, 4, + 1, 2, 3, 11, 4, 1, 2, 3, nil, 4 ] + +racc_action_check = [ + 0, 0, 0, 5, 0, 3, 3, 3, 9, 3, + 8, 8, 8, 8, 8, 6, 6, 6, nil, 6 ] + +racc_action_pointer = [ + -2, nil, nil, 3, nil, 3, 13, nil, 8, 8, + nil, nil, nil ] + +racc_action_default = [ + -8, -4, -5, -8, -7, -8, -1, -3, -8, -8, + -2, -6, 13 ] + +racc_goto_table = [ + 6, 5, 10, 8, 10 ] + +racc_goto_check = [ + 2, 1, 3, 2, 3 ] + +racc_goto_pointer = [ + nil, 1, 0, -4 ] + +racc_goto_default = [ + nil, nil, nil, 7 ] + +racc_reduce_table = [ + 0, 0, :racc_error, + 1, 8, :_reduce_1, + 2, 9, :_reduce_2, + 1, 9, :_reduce_none, + 1, 10, :_reduce_4, + 1, 10, :_reduce_5, + 3, 10, :_reduce_6, + 1, 10, :_reduce_7 ] + +racc_reduce_n = 8 + +racc_shift_n = 13 + +racc_token_table = { + false => 0, + :error => 1, + :PARAM => 2, + :GLOB => 3, + :LPAREN => 4, + :RPAREN => 5, + :CHAR => 6 } + +racc_nt_base = 7 + +racc_use_result_var = true + +Racc_arg = [ + racc_action_table, + racc_action_check, + racc_action_default, + racc_action_pointer, + racc_goto_table, + racc_goto_check, + racc_goto_default, + racc_goto_pointer, + racc_nt_base, + racc_reduce_table, + racc_token_table, + racc_shift_n, + racc_reduce_n, + racc_use_result_var ] + +Racc_token_to_s_table = [ + "$end", + "error", + "PARAM", + "GLOB", + "LPAREN", + "RPAREN", + "CHAR", + "$start", + "target", + "expr", + "token" ] + +Racc_debug_parser = false + +##### State transition tables end ##### + +# reduce 0 omitted + +def _reduce_1(val, _values, result) + result = anchor ? "\\A#{val.join}\\Z" : "\\A#{val.join}" + result +end + +def _reduce_2(val, _values, result) + result = val.join + result +end + +# reduce 3 omitted + +def _reduce_4(val, _values, result) + name = val[0].to_sym + requirement = requirements[name] + result = REGEXP_NAMED_CAPTURE % [name, requirement] + + result +end + +def _reduce_5(val, _values, result) + name = val[0].to_sym + requirement = requirements[name] + result = REGEXP_NAMED_CAPTURE % [name, '.+' || requirement] + + result +end + +def _reduce_6(val, _values, result) + result = "(?:#{val[1]})?" + result +end + +def _reduce_7(val, _values, result) + result = Regexp.escape(val[0]) + result +end + +def _reduce_none(val, _values, result) + val[0] +end + + end # class StrexpParser + end # module Mount +end # module Rack diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.y b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.y new file mode 100644 index 00000000000..ffbd9fae11d --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.y @@ -0,0 +1,34 @@ +class Rack::Mount::StrexpParser +rule + target: expr { result = anchor ? "\\A#{val.join}\\Z" : "\\A#{val.join}" } + + expr: expr token { result = val.join } + | token + + token: PARAM { + name = val[0].to_sym + requirement = requirements[name] + result = REGEXP_NAMED_CAPTURE % [name, requirement] + } + | GLOB { + name = val[0].to_sym + requirement = requirements[name] + result = REGEXP_NAMED_CAPTURE % [name, '.+' || requirement] + } + | LPAREN expr RPAREN { result = "(?:#{val[1]})?" } + | CHAR { result = Regexp.escape(val[0]) } +end + +---- header ---- +require 'rack/mount/utils' +require 'rack/mount/strexp/tokenizer' + +---- inner + +if Regin.regexp_supports_named_captures? + REGEXP_NAMED_CAPTURE = '(?<%s>%s)'.freeze +else + REGEXP_NAMED_CAPTURE = '(?:<%s>%s)'.freeze +end + +attr_accessor :anchor, :requirements diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rb new file mode 100644 index 00000000000..0ff7f676617 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rb @@ -0,0 +1,83 @@ +#-- +# DO NOT MODIFY!!!! +# This file is automatically generated by rex 1.0.5.beta1 +# from lexical definition file "lib/rack/mount/strexp/tokenizer.rex". +#++ + +require 'racc/parser' +class Rack::Mount::StrexpParser < Racc::Parser + require 'strscan' + + class ScanError < StandardError ; end + + attr_reader :lineno + attr_reader :filename + attr_accessor :state + + def scan_setup(str) + @ss = StringScanner.new(str) + @lineno = 1 + @state = nil + end + + def action + yield + end + + def scan_str(str) + scan_setup(str) + do_parse + end + alias :scan :scan_str + + def load_file( filename ) + @filename = filename + open(filename, "r") do |f| + scan_setup(f.read) + end + end + + def scan_file( filename ) + load_file(filename) + do_parse + end + + + def next_token + return if @ss.eos? + + text = @ss.peek(1) + @lineno += 1 if text == "\n" + token = case @state + when nil + case + when (text = @ss.scan(/\\(\(|\)|:|\*)/)) + action { [:CHAR, @ss[1]] } + + when (text = @ss.scan(/\:([a-zA-Z_]\w*)/)) + action { [:PARAM, @ss[1]] } + + when (text = @ss.scan(/\*([a-zA-Z_]\w*)/)) + action { [:GLOB, @ss[1]] } + + when (text = @ss.scan(/\(/)) + action { [:LPAREN, text] } + + when (text = @ss.scan(/\)/)) + action { [:RPAREN, text] } + + when (text = @ss.scan(/./)) + action { [:CHAR, text] } + + else + text = @ss.string[@ss.pos .. -1] + raise ScanError, "can not match: '" + text + "'" + end # if + + else + raise ScanError, "undefined state: '" + state.to_s + "'" + end # case state + token + end # def next_token + +end # class diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rex b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rex new file mode 100644 index 00000000000..473bd096e17 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rex @@ -0,0 +1,12 @@ +class Rack::Mount::StrexpParser +macro + RESERVED \(|\)|:|\* + ALPHA_U [a-zA-Z_] +rule + \\({RESERVED}) { [:CHAR, @ss[1]] } + \:({ALPHA_U}\w*) { [:PARAM, @ss[1]] } + \*({ALPHA_U}\w*) { [:GLOB, @ss[1]] } + \( { [:LPAREN, text] } + \) { [:RPAREN, text] } + . { [:CHAR, text] } +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/utils.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/utils.rb new file mode 100644 index 00000000000..aa23b1162f9 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/utils.rb @@ -0,0 +1,148 @@ +begin + require 'regin' +rescue LoadError + $: << File.expand_path(File.join(File.dirname(__FILE__), 'vendor/regin')) + require 'regin' +end + +require 'uri' + +module Rack::Mount + # Private utility methods used throughout Rack::Mount. + #-- + # This module is a trash can. Try to move these functions into + # more appropriate contexts. + #++ + module Utils + # Normalizes URI path. + # + # Strips off trailing slash and ensures there is a leading slash. + # + # normalize_path("/foo") # => "/foo" + # normalize_path("/foo/") # => "/foo" + # normalize_path("foo") # => "/foo" + # normalize_path("") # => "/" + def normalize_path(path) + path = "/#{path}" + path.squeeze!('/') + path.sub!(%r{/+\Z}, '') + path = '/' if path == '' + path + end + module_function :normalize_path + + # Removes trailing nils from array. + # + # pop_trailing_nils!([1, 2, 3]) # => [1, 2, 3] + # pop_trailing_nils!([1, 2, 3, nil, nil]) # => [1, 2, 3] + # pop_trailing_nils!([nil]) # => [] + def pop_trailing_nils!(ary) + while ary.length > 0 && ary.last.nil? + ary.pop + end + ary + end + module_function :pop_trailing_nils! + + RESERVED_PCHAR = ':@&=+$,;%' + SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}" + if RUBY_VERSION >= '1.9' + UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false).freeze + else + UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze + end + + Parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI + + def escape_uri(uri) + Parser.escape(uri.to_s, UNSAFE_PCHAR) + end + module_function :escape_uri + + if ''.respond_to?(:force_encoding) + def unescape_uri(uri) + Parser.unescape(uri).force_encoding('utf-8') + end + else + def unescape_uri(uri) + URI.unescape(uri) + end + end + module_function :unescape_uri + + # Taken from Rack 1.1.x to build nested query strings + def build_nested_query(value, prefix = nil) #:nodoc: + case value + when Array + value.map { |v| + build_nested_query(v, "#{prefix}[]") + }.join("&") + when Hash + value.map { |k, v| + build_nested_query(v, prefix ? "#{prefix}[#{Rack::Utils.escape(k)}]" : Rack::Utils.escape(k)) + }.join("&") + when String + raise ArgumentError, "value must be a Hash" if prefix.nil? + "#{prefix}=#{Rack::Utils.escape(value)}" + else + prefix + end + end + module_function :build_nested_query + + # Determines whether the regexp must match the entire string. + # + # regexp_anchored?(/^foo$/) # => true + # regexp_anchored?(/foo/) # => false + # regexp_anchored?(/^foo/) # => false + # regexp_anchored?(/foo$/) # => false + def regexp_anchored?(regexp) + regexp.source =~ /\A(\\A|\^).*(\\Z|\$)\Z/m ? true : false + end + module_function :regexp_anchored? + + def normalize_extended_expression(regexp) + return regexp unless regexp.options & Regexp::EXTENDED != 0 + source = regexp.source + source.gsub!(/#.+$/, '') + source.gsub!(/\s+/, '') + source.gsub!(/\\\//, '/') + Regexp.compile(source) + end + module_function :normalize_extended_expression + + def parse_regexp(regexp) + cache = @@_parse_regexp_cache ||= {} + + if expression = cache[regexp] + return expression + end + + unless regexp.is_a?(RegexpWithNamedGroups) + regexp = RegexpWithNamedGroups.new(regexp) + end + + expression = Regin.parse(regexp) + + unless Regin.regexp_supports_named_captures? + tag_captures = Proc.new do |group| + case group + when Regin::Group + # TODO: dup instead of mutating + group.instance_variable_set('@name', regexp.names[group.index]) if group.index + tag_captures.call(group.expression) + when Regin::Expression + group.each { |child| tag_captures.call(child) } + end + end + tag_captures.call(expression) + end + + cache[regexp] = expression.freeze + expression + rescue Racc::ParseError, Regin::Parser::ScanError + [] + end + module_function :parse_regexp + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multimap.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multimap.rb new file mode 100644 index 00000000000..0b49b492804 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multimap.rb @@ -0,0 +1,569 @@ +require 'forwardable' +require 'multiset' + +# Multimap is a generalization of a map or associative array +# abstract data type in which more than one value may be associated +# with and returned for a given key. +# +# == Example +# +# require 'multimap' +# map = Multimap.new +# map["a"] = 100 +# map["b"] = 200 +# map["a"] = 300 +# map["a"] # -> [100, 300] +# map["b"] # -> [200] +# map.keys # -> # +class Multimap + extend Forwardable + + include Enumerable + + # call-seq: + # Multimap[ [key =>|, value]* ] => multimap + # + # Creates a new multimap populated with the given objects. + # + # Multimap["a", 100, "b", 200] #=> {"a"=>[100], "b"=>[200]} + # Multimap["a" => 100, "b" => 200] #=> {"a"=>[100], "b"=>[200]} + def self.[](*args) + default = [] + + if args.size == 2 && args.last.is_a?(Hash) + default = args.shift + elsif !args.first.is_a?(Hash) && args.size % 2 == 1 + default = args.shift + end + + if args.size == 1 && args.first.is_a?(Hash) + args[0] = args.first.inject({}) { |hash, (key, value)| + unless value.is_a?(default.class) + value = (default.dup << value) + end + hash[key] = value + hash + } + else + index = 0 + args.map! { |value| + unless index % 2 == 0 || value.is_a?(default.class) + value = (default.dup << value) + end + index += 1 + value + } + end + + map = new + map.instance_variable_set(:@hash, Hash[*args]) + map.default = default + map + end + + # call-seq: + # Multimap.new => multimap + # Multimap.new(default) => multimap + # + # Returns a new, empty multimap. + # + # map = Multimap.new(Set.new) + # h["a"] = 100 + # h["b"] = 200 + # h["a"] #=> [100].to_set + # h["c"] #=> [].to_set + def initialize(default = []) + @hash = Hash.new(default) + end + + def initialize_copy(original) #:nodoc: + @hash = Hash.new(original.default.dup) + original._internal_hash.each_pair do |key, container| + @hash[key] = container.dup + end + end + + def_delegators :@hash, :clear, :default, :default=, :empty?, + :fetch, :has_key?, :key? + + # Retrieves the value object corresponding to the + # *keys object. + def [](key) + @hash[key] + end + + # call-seq: + # map[key] = value => value + # map.store(key, value) => value + # + # Associates the value given by value with the key + # given by key. Unlike a regular hash, multiple can be + # assoicated with the same value. + # + # map = Multimap["a" => 100, "b" => 200] + # map["a"] = 9 + # map["c"] = 4 + # map #=> {"a" => [100, 9], "b" => [200], "c" => [4]} + def store(key, value) + update_container(key) do |container| + container << value + container + end + end + alias_method :[]=, :store + + # call-seq: + # map.delete(key, value) => value + # map.delete(key) => value + # + # Deletes and returns a key-value pair from map. If only + # key is given, all the values matching that key will be + # deleted. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.delete("b", 300) #=> 300 + # map.delete("a") #=> [100] + def delete(key, value = nil) + if value + @hash[key].delete(value) + else + @hash.delete(key) + end + end + + # call-seq: + # map.each { |key, value| block } => map + # + # Calls block for each key/value pair in map, passing + # the key and value to the block as a two-element array. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.each { |key, value| puts "#{key} is #{value}" } + # + # produces: + # + # a is 100 + # b is 200 + # b is 300 + def each + each_pair do |key, value| + yield [key, value] + end + end + + # call-seq: + # map.each_association { |key, container| block } => map + # + # Calls block once for each key/container in map, passing + # the key and container to the block as parameters. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.each_association { |key, container| puts "#{key} is #{container}" } + # + # produces: + # + # a is [100] + # b is [200, 300] + def each_association(&block) + @hash.each_pair(&block) + end + + # call-seq: + # map.each_container { |container| block } => map + # + # Calls block for each container in map, passing the + # container as a parameter. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.each_container { |container| puts container } + # + # produces: + # + # [100] + # [200, 300] + def each_container + each_association do |_, container| + yield container + end + end + + # call-seq: + # map.each_key { |key| block } => map + # + # Calls block for each key in hsh, passing the key + # as a parameter. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.each_key { |key| puts key } + # + # produces: + # + # a + # b + # b + def each_key + each_pair do |key, _| + yield key + end + end + + # call-seq: + # map.each_pair { |key_value_array| block } => map + # + # Calls block for each key/value pair in map, + # passing the key and value as parameters. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.each_pair { |key, value| puts "#{key} is #{value}" } + # + # produces: + # + # a is 100 + # b is 200 + # b is 300 + def each_pair + each_association do |key, values| + values.each do |value| + yield key, value + end + end + end + + # call-seq: + # map.each_value { |value| block } => map + # + # Calls block for each key in map, passing the + # value as a parameter. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.each_value { |value| puts value } + # + # produces: + # + # 100 + # 200 + # 300 + def each_value + each_pair do |_, value| + yield value + end + end + + def ==(other) #:nodoc: + case other + when Multimap + @hash == other._internal_hash + else + @hash == other + end + end + + def eql?(other) #:nodoc: + case other + when Multimap + @hash.eql?(other._internal_hash) + else + @hash.eql?(other) + end + end + + def freeze #:nodoc: + each_container { |container| container.freeze } + default.freeze + super + end + + # call-seq: + # map.has_value?(value) => true or false + # map.value?(value) => true or false + # + # Returns true if the given value is present for any key + # in map. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.has_value?(300) #=> true + # map.has_value?(999) #=> false + def has_value?(value) + values.include?(value) + end + alias_method :value?, :has_value? + + # call-seq: + # map.index(value) => key + # + # Returns the key for a given value. If not found, returns + # nil. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.index(100) #=> "a" + # map.index(200) #=> "b" + # map.index(999) #=> nil + def index(value) + invert[value] + end + + # call-seq: + # map.delete_if {| key, value | block } -> map + # + # Deletes every key-value pair from map for which block + # evaluates to true. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.delete_if {|key, value| value >= 300 } + # #=> Multimap["a" => 100, "b" => 200] + # + def delete_if + each_association do |key, container| + container.delete_if do |value| + yield [key, value] + end + end + self + end + + # call-seq: + # map.reject {| key, value | block } -> map + # + # Same as Multimap#delete_if, but works on (and returns) a + # copy of the map. Equivalent to + # map.dup.delete_if. + # + def reject(&block) + dup.delete_if(&block) + end + + # call-seq: + # map.reject! {| key, value | block } -> map or nil + # + # Equivalent to Multimap#delete_if, but returns + # nil if no changes were made. + # + def reject!(&block) + old_size = size + delete_if(&block) + old_size == size ? nil : self + end + + # call-seq: + # map.replace(other_map) => map + # + # Replaces the contents of map with the contents of + # other_map. + # + # map = Multimap["a" => 100, "b" => 200] + # map.replace({ "c" => 300, "d" => 400 }) + # #=> Multimap["c" => 300, "d" => 400] + def replace(other) + case other + when Array + @hash.replace(self.class[self.default, *other]) + when Hash + @hash.replace(self.class[self.default, other]) + when self.class + @hash.replace(other) + else + raise ArgumentError + end + end + + # call-seq: + # map.invert => multimap + # + # Returns a new multimap created by using map's values as keys, + # and the keys as values. + # + # map = Multimap["n" => 100, "m" => 100, "d" => [200, 300]] + # map.invert #=> Multimap[100 => ["n", "m"], 200 => "d", 300 => "d"] + def invert + h = self.class.new(default.dup) + each_pair { |key, value| h[value] = key } + h + end + + # call-seq: + # map.keys => multiset + # + # Returns a new +Multiset+ populated with the keys from this hash. See also + # Multimap#values and Multimap#containers. + # + # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400] + # map.keys #=> Multiset.new(["a", "b", "b", "c"]) + def keys + keys = Multiset.new + each_key { |key| keys << key } + keys + end + + # Returns true if the given key is present in Multimap. + def include?(key) + keys.include?(key) + end + alias_method :member?, :include? + + # call-seq: + # map.length => fixnum + # map.size => fixnum + # + # Returns the number of key-value pairs in the map. + # + # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400] + # map.length #=> 4 + # map.delete("a") #=> 100 + # map.length #=> 3 + def size + values.size + end + alias_method :length, :size + + # call-seq: + # map.merge(other_map) => multimap + # + # Returns a new multimap containing the contents of other_map and + # the contents of map. + # + # map1 = Multimap["a" => 100, "b" => 200] + # map2 = Multimap["a" => 254, "c" => 300] + # map2.merge(map2) #=> Multimap["a" => 100, "b" => [200, 254], "c" => 300] + # map1 #=> Multimap["a" => 100, "b" => 200] + def merge(other) + dup.update(other) + end + + # call-seq: + # map.merge!(other_map) => multimap + # map.update(other_map) => multimap + # + # Adds each pair from other_map to map. + # + # map1 = Multimap["a" => 100, "b" => 200] + # map2 = Multimap["b" => 254, "c" => 300] + # + # map1.merge!(map2) + # #=> Multimap["a" => 100, "b" => [200, 254], "c" => 300] + def update(other) + case other + when self.class + other.each_pair { |key, value| store(key, value) } + when Hash + update(self.class[self.default, other]) + else + raise ArgumentError + end + self + end + alias_method :merge!, :update + + # call-seq: + # map.select { |key, value| block } => multimap + # + # Returns a new Multimap consisting of the pairs for which the + # block returns true. + # + # map = Multimap["a" => 100, "b" => 200, "c" => 300] + # map.select { |k,v| k > "a" } #=> Multimap["b" => 200, "c" => 300] + # map.select { |k,v| v < 200 } #=> Multimap["a" => 100] + def select + inject(self.class.new) { |map, (key, value)| + map[key] = value if yield([key, value]) + map + } + end + + # call-seq: + # map.to_a => array + # + # Converts map to a nested array of [key, + # value] arrays. + # + # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400] + # map.to_a #=> [["a", 100], ["b", 200], ["b", 300], ["c", 400]] + def to_a + ary = [] + each_pair do |key, value| + ary << [key, value] + end + ary + end + + # call-seq: + # map.to_hash => hash + # + # Converts map to a basic hash. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.to_hash #=> { "a" => [100], "b" => [200, 300] } + def to_hash + @hash.dup + end + + # call-seq: + # map.containers => array + # + # Returns a new array populated with the containers from map. See + # also Multimap#keys and Multimap#values. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.containers #=> [[100], [200, 300]] + def containers + containers = [] + each_container { |container| containers << container } + containers + end + + # call-seq: + # map.values => array + # + # Returns a new array populated with the values from map. See + # also Multimap#keys and Multimap#containers. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.values #=> [100, 200, 300] + def values + values = [] + each_value { |value| values << value } + values + end + + # Return an array containing the values associated with the given keys. + def values_at(*keys) + @hash.values_at(*keys) + end + + def marshal_dump #:nodoc: + @hash + end + + def marshal_load(hash) #:nodoc: + @hash = hash + end + + def to_yaml(opts = {}) #:nodoc: + YAML::quick_emit(self, opts) do |out| + out.map(taguri, to_yaml_style) do |map| + @hash.each do |k, v| + map.add(k, v) + end + map.add('__default__', @hash.default) + end + end + end + + def yaml_initialize(tag, val) #:nodoc: + default = val.delete('__default__') + @hash = val + @hash.default = default + self + end + + protected + def _internal_hash #:nodoc: + @hash + end + + def update_container(key) #:nodoc: + container = @hash[key] + container = container.dup if container.equal?(default) + container = yield(container) + @hash[key] = container + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multiset.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multiset.rb new file mode 100644 index 00000000000..119bf126468 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multiset.rb @@ -0,0 +1,185 @@ +require 'set' + +# Multiset implements a collection of unordered values and +# allows duplicates. +# +# == Example +# +# require 'multiset' +# s1 = Multiset.new [1, 2] # -> # +# s1.add(2) # -> # +# s1.merge([2, 6]) # -> # +# s1.multiplicity(2) # -> 3 +# s1.multiplicity(3) # -> 1 +class Multiset < Set + def initialize(*args, &block) #:nodoc: + @hash = Hash.new(0) + super + end + + # Returns the number of times an element belongs to the multiset. + def multiplicity(e) + @hash[e] + end + + # Returns the total number of elements in a multiset, including + # repeated memberships + def cardinality + @hash.inject(0) { |s, (e, m)| s += m } + end + alias_method :size, :cardinality + alias_method :length, :cardinality + + # Converts the set to an array. The order of elements is uncertain. + def to_a + inject([]) { |ary, (key, _)| ary << key } + end + + # Returns true if the set is a superset of the given set. + def superset?(set) + set.is_a?(self.class) or raise ArgumentError, "value must be a set" + return false if cardinality < set.cardinality + set.all? { |o| set.multiplicity(o) <= multiplicity(o) } + end + + # Returns true if the set is a proper superset of the given set. + def proper_superset?(set) + set.is_a?(self.class) or raise ArgumentError, "value must be a set" + return false if cardinality <= set.cardinality + set.all? { |o| set.multiplicity(o) <= multiplicity(o) } + end + + # Returns true if the set is a subset of the given set. + def subset?(set) + set.is_a?(self.class) or raise ArgumentError, "value must be a set" + return false if set.cardinality < cardinality + all? { |o| multiplicity(o) <= set.multiplicity(o) } + end + + # Returns true if the set is a proper subset of the given set. + def proper_subset?(set) + set.is_a?(self.class) or raise ArgumentError, "value must be a set" + return false if set.cardinality <= cardinality + all? { |o| multiplicity(o) <= set.multiplicity(o) } + end + + # Calls the given block once for each element in the set, passing + # the element as parameter. Returns an enumerator if no block is + # given. + def each + @hash.each_pair do |key, multiplicity| + multiplicity.times do + yield(key) + end + end + self + end + + # Adds the given object to the set and returns self. Use +merge+ to + # add many elements at once. + def add(o) + @hash[o] ||= 0 + @hash[o] += 1 + self + end + alias << add + + undef :add? + + # Deletes all the identical object from the set and returns self. + # If +n+ is given, it will remove that amount of identical objects + # from the set. Use +subtract+ to delete many different items at + # once. + def delete(o, n = nil) + if n + @hash[o] ||= 0 + @hash[o] -= n if @hash[o] > 0 + @hash.delete(o) if @hash[o] == 0 + else + @hash.delete(o) + end + self + end + + undef :delete? + + # Deletes every element of the set for which block evaluates to + # true, and returns self. + def delete_if + each { |o| delete(o) if yield(o) } + self + end + + # Merges the elements of the given enumerable object to the set and + # returns self. + def merge(enum) + enum.each { |o| add(o) } + self + end + + # Deletes every element that appears in the given enumerable object + # and returns self. + def subtract(enum) + enum.each { |o| delete(o, 1) } + self + end + + # Returns a new set containing elements common to the set and the + # given enumerable object. + def &(enum) + s = dup + n = self.class.new + enum.each { |o| + if s.include?(o) + s.delete(o, 1) + n.add(o) + end + } + n + end + alias intersection & + + # Returns a new set containing elements exclusive between the set + # and the given enumerable object. (set ^ enum) is equivalent to + # ((set | enum) - (set & enum)). + def ^(enum) + n = self.class.new(enum) + each { |o| n.include?(o) ? n.delete(o, 1) : n.add(o) } + n + end + + # Returns true if two sets are equal. Two multisets are equal if + # they have the same cardinalities and each element has the same + # multiplicity in both sets. The equality of each element inside + # the multiset is defined according to Object#eql?. + def eql?(set) + return true if equal?(set) + set = self.class.new(set) unless set.is_a?(self.class) + return false unless cardinality == set.cardinality + superset?(set) && subset?(set) + end + alias_method :==, :eql? + + def marshal_dump #:nodoc: + @hash + end + + def marshal_load(hash) #:nodoc: + @hash = hash + end + + def to_yaml(opts = {}) #:nodoc: + YAML::quick_emit(self, opts) do |out| + out.map(taguri, to_yaml_style) do |map| + @hash.each do |k, v| + map.add(k, v) + end + end + end + end + + def yaml_initialize(tag, val) #:nodoc: + @hash = val + self + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/nested_multimap.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/nested_multimap.rb new file mode 100644 index 00000000000..4eb088b91a9 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/nested_multimap.rb @@ -0,0 +1,158 @@ +require 'multimap' + +# NestedMultimap allows values to be assoicated with a nested +# set of keys. +class NestedMultimap < Multimap + # call-seq: + # multimap[*keys] = value => value + # multimap.store(*keys, value) => value + # + # Associates the value given by value with multiple key + # given by keys. + # + # map = NestedMultimap.new + # map["a"] = 100 + # map["a", "b"] = 101 + # map["a"] = 102 + # map #=> {"a"=>{"b"=>[100, 101, 102], default => [100, 102]}} + def store(*args) + keys = args + value = args.pop + + raise ArgumentError, 'wrong number of arguments (1 for 2)' unless value + + if keys.length > 1 + update_container(keys.shift) do |container| + container = self.class.new(container) unless container.is_a?(self.class) + container[*keys] = value + container + end + elsif keys.length == 1 + super(keys.first, value) + else + self << value + end + end + alias_method :[]=, :store + + # call-seq: + # multimap << obj => multimap + # + # Pushes the given object on to the end of all the containers. + # + # map = NestedMultimap["a" => [100], "b" => [200, 300]] + # map << 300 + # map["a"] #=> [100, 300] + # map["c"] #=> [300] + def <<(value) + @hash.each_value { |container| container << value } + self.default << value + self + end + + # call-seq: + # multimap[*keys] => value + # multimap[key1, key2, key3] => value + # + # Retrieves the value object corresponding to the + # *keys object. + def [](*keys) + i, l, r, k = 0, keys.length, self, self.class + while r.is_a?(k) + r = i < l ? r._internal_hash[keys[i]] : r.default + i += 1 + end + r + end + + # call-seq: + # multimap.each_association { |key, container| block } => multimap + # + # Calls block once for each key/container in map, passing + # the key and container to the block as parameters. + # + # map = NestedMultimap.new + # map["a"] = 100 + # map["a", "b"] = 101 + # map["a"] = 102 + # map["c"] = 200 + # map.each_association { |key, container| puts "#{key} is #{container}" } + # + # produces: + # + # ["a", "b"] is [100, 101, 102] + # "c" is [200] + def each_association + super() do |key, container| + if container.respond_to?(:each_association) + container.each_association do |nested_key, value| + yield [key, nested_key].flatten, value + end + else + yield key, container + end + end + end + + # call-seq: + # multimap.each_container_with_default { |container| block } => map + # + # Calls block for every container in map including + # the default, passing the container as a parameter. + # + # map = NestedMultimap.new + # map["a"] = 100 + # map["a", "b"] = 101 + # map["a"] = 102 + # map.each_container_with_default { |container| puts container } + # + # produces: + # + # [100, 101, 102] + # [100, 102] + # [] + def each_container_with_default(&block) + @hash.each_value do |container| + iterate_over_container(container, &block) + end + iterate_over_container(default, &block) + self + end + + # call-seq: + # multimap.containers_with_default => array + # + # Returns a new array populated with all the containers from + # map including the default. + # + # map = NestedMultimap.new + # map["a"] = 100 + # map["a", "b"] = 101 + # map["a"] = 102 + # map.containers_with_default #=> [[100, 101, 102], [100, 102], []] + def containers_with_default + containers = [] + each_container_with_default { |container| containers << container } + containers + end + + def inspect #:nodoc: + super.gsub(/\}$/, ", default => #{default.inspect}}") + end + + private + def iterate_over_container(container) + if container.respond_to?(:each_container_with_default) + container.each_container_with_default do |value| + yield value + end + else + yield container + end + end +end + +begin + require 'nested_multimap_ext' +rescue LoadError +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin.rb new file mode 100644 index 00000000000..d38922bcc6e --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin.rb @@ -0,0 +1,45 @@ +module Regin + autoload :Alternation, 'regin/alternation' + autoload :Anchor, 'regin/anchor' + autoload :Atom, 'regin/atom' + autoload :Character, 'regin/character' + autoload :CharacterClass, 'regin/character_class' + autoload :Collection, 'regin/collection' + autoload :Expression, 'regin/expression' + autoload :Group, 'regin/group' + autoload :Options, 'regin/options' + autoload :Parser, 'regin/parser' + + class << self + begin + eval('foo = /(?.*)/').named_captures + + # Returns true if the interpreter is using the Oniguruma Regexp lib + # and supports named captures. + # + # /(?bar)/ + def regexp_supports_named_captures? + true + end + rescue SyntaxError, NoMethodError + def regexp_supports_named_captures? #:nodoc: + false + end + end + + # Parses Regexp and returns a Expression data structure. + def parse(regexp) + Parser.parse_regexp(regexp) + end + + # Recompiles Regexp by parsing it and turning it back into a Regexp. + # + # (In the future Regin will perform some Regexp optimizations + # such as removing unnecessary captures and options) + def compile(source) + regexp = Regexp.compile(source) + expression = parse(regexp) + Regexp.compile(expression.to_s(true), expression.flags) + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/alternation.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/alternation.rb new file mode 100644 index 00000000000..ce4f52bfdb7 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/alternation.rb @@ -0,0 +1,40 @@ +module Regin + class Alternation < Collection + def initialize(*args) + args, options = extract_options(args) + + if args.length == 1 && args.first.instance_of?(Array) + super(args.first) + else + super(args) + end + + if options.key?(:ignorecase) + @array.map! { |e| e.dup(:ignorecase => options[:ignorecase]) } + end + end + + # Returns true if expression could be treated as a literal string. + # + # Alternation groups are never literal. + def literal? + false + end + + def flags + 0 + end + + def dup(options = {}) + self.class.new(to_a, options) + end + + def to_s(parent = false) + map { |e| e.to_s(parent) }.join('|') + end + + def inspect #:nodoc: + to_s.inspect + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/anchor.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/anchor.rb new file mode 100644 index 00000000000..05520dd5e0a --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/anchor.rb @@ -0,0 +1,4 @@ +module Regin + class Anchor < Atom + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/atom.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/atom.rb new file mode 100644 index 00000000000..eb1923a5a1f --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/atom.rb @@ -0,0 +1,59 @@ +module Regin + class Atom + attr_reader :value, :ignorecase + + def initialize(value, options = {}) + @value = value + @ignorecase = options[:ignorecase] + end + + def option_names + %w( ignorecase ) + end + + # Returns true if expression could be treated as a literal string. + def literal? + false + end + + def casefold? + ignorecase ? true : false + end + + def dup(options = {}) + original_options = option_names.inject({}) do |h, m| + h[m.to_sym] = send(m) + h + end + self.class.new(value, original_options.merge(options)) + end + + def to_s(parent = false) + "#{value}" + end + + def inspect #:nodoc: + "#<#{self.class.to_s.sub('Regin::', '')} #{to_s.inspect}>" + end + + def ==(other) #:nodoc: + case other + when String + other == to_s + else + eql?(other) + end + end + + def eql?(other) #:nodoc: + other.instance_of?(self.class) && + self.value.eql?(other.value) && + (!!self.ignorecase).eql?(!!other.ignorecase) + end + + def freeze #:nodoc: + value.freeze + super + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character.rb new file mode 100644 index 00000000000..12a9199d2a6 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character.rb @@ -0,0 +1,56 @@ +module Regin + class Character < Atom + attr_reader :quantifier + + def initialize(value, options = {}) + @quantifier = options[:quantifier] + super + end + + def option_names + %w( quantifier ) + super + end + + # Returns true if expression could be treated as a literal string. + # + # A Character is literal is there is no quantifier attached to it. + def literal? + quantifier.nil? && !ignorecase + end + + def to_s(parent = false) + if !parent && ignorecase + "(?i-mx:#{value})#{quantifier}" + else + "#{value}#{quantifier}" + end + end + + def to_regexp(anchored = false) + re = to_s(true) + re = "\\A#{re}\\Z" if anchored + Regexp.compile(re, ignorecase) + end + + def match(char) + to_regexp(true).match(char) + end + + def include?(char) + if ignorecase + value.downcase == char.downcase + else + value == char + end + end + + def eql?(other) #:nodoc: + super && quantifier.eql?(other.quantifier) + end + + def freeze #:nodoc: + quantifier.freeze if quantifier + super + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character_class.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character_class.rb new file mode 100644 index 00000000000..caed5ef9d09 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character_class.rb @@ -0,0 +1,55 @@ +module Regin + class CharacterClass < Character + def initialize(value, options = {}) + @negate = options[:negate] + super + end + + def option_names + %w( negate ) + super + end + + attr_reader :negate + + def negated? + negate ? true : false + end + + # Returns true if expression could be treated as a literal string. + # + # A CharacterClass is never literal. + def literal? + false + end + + def bracketed? + value != '.' && value !~ /^\\[dDsSwW]$/ + end + + def to_s(parent = false) + if bracketed? + if !parent && ignorecase + "(?i-mx:[#{negate && '^'}#{value}])#{quantifier}" + else + "[#{negate && '^'}#{value}]#{quantifier}" + end + else + super + end + end + + def include?(char) + re = quantifier ? to_s.sub(/#{Regexp.escape(quantifier)}$/, '') : to_s + Regexp.compile("\\A#{re}\\Z").match(char) + end + + def eql?(other) #:nodoc: + super && negate == other.negate + end + + def freeze #:nodoc: + negate.freeze if negate + super + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/collection.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/collection.rb new file mode 100644 index 00000000000..b60353268a0 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/collection.rb @@ -0,0 +1,83 @@ +module Regin + class Collection + include Enumerable + + def initialize(*args) + @array = Array.new(*args) + end + + def each + @array.each{ |item| yield item } + end + + def [](i) + @array[i] + end + + def length + @array.length + end + alias_method :size, :length + + def first + @array.first + end + + def last + @array.last + end + + def +(other) + ary = other.is_a?(self.class) ? other.internal_array : other + self.class.new(@array + ary) + end + + def to_regexp(anchored = false) + re = to_s(true) + re = "\\A#{re}\\Z" if anchored + Regexp.compile(re, flags) + end + + def match(char) + to_regexp.match(char) + end + + def include?(char) + any? { |e| e.include?(char) } + end + + def ==(other) #:nodoc: + case other + when String + other == to_s + when Array + other == @array + else + eql?(other) + end + end + + def eql?(other) #:nodoc: + other.instance_of?(self.class) && @array.eql?(other.internal_array) + end + + def freeze #:nodoc: + each { |e| e.freeze } + @array.freeze + super + end + + protected + def internal_array #:nodoc: + @array + end + + def extract_options(args) + if args.last.is_a?(Hash) + return args[0..-2], args.last + else + return args, {} + end + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/expression.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/expression.rb new file mode 100644 index 00000000000..18e49650971 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/expression.rb @@ -0,0 +1,126 @@ +module Regin + class Expression < Collection + attr_reader :ignorecase, :multiline, :extended + + def initialize(*args) + args, options = extract_options(args) + + @multiline = @ignorecase = @extended = nil + + if args.length == 1 && args.first.instance_of?(Array) + super(args.first) + else + args = args.map { |e| e.instance_of?(String) ? Character.new(e) : e } + super(args) + end + + self.multiline = options[:multiline] if options.key?(:multiline) + self.ignorecase = options[:ignorecase] if options.key?(:ignorecase) + self.extended = options[:extended] if options.key?(:extended) + end + + # Returns true if expression could be treated as a literal string. + # + # A Expression is literal if all its elements are literal. + def literal? + !ignorecase && all? { |e| e.literal? } + end + + def anchored? + anchored_to_start? && anchored_to_end? + end + + def anchored_to_start? + first.is_a?(Anchor) && first == '\A' + end + + def anchored_to_end? + last.is_a?(Anchor) && last == '\Z' + end + + def anchored_to_line? + anchored_to_start_of_line? && anchored_to_end_of_line? + end + + def anchored_to_start_of_line? + anchored_to_start? || (first.is_a?(Anchor) && first == '^') + end + + def anchored_to_end_of_line? + anchored_to_end? || (last.is_a?(Anchor) && last == '$') + end + + def options? + options.any?(true) + end + + def flags + options.to_i + end + + def +(other) + ary = other.is_a?(self.class) ? other.internal_array : other + ary = @array + ary + [options.to_h(true)] + self.class.new(*ary) + end + + def dup(options = {}) + expression = super() + expression.multiline = options[:multiline] if options.key?(:multiline) + expression.ignorecase = options[:ignorecase] if options.key?(:ignorecase) + expression.extended = options[:extended] if options.key?(:extended) + expression + end + + def to_s(parent = false) + if parent || !options? + map { |e| e.to_s(parent) }.join + else + with, without = [], [] + multiline ? (with << 'm') : (without << 'm') + ignorecase ? (with << 'i') : (without << 'i') + extended ? (with << 'x') : (without << 'x') + + with = with.join + without = without.any? ? "-#{without.join}" : '' + + "(?#{with}#{without}:#{map { |e| e.to_s(true) }.join})" + end + end + + def inspect #:nodoc: + "#" + end + + def casefold? + ignorecase + end + + def eql?(other) #:nodoc: + super && + !!self.multiline == !!other.multiline && + !!self.ignorecase == !!other.ignorecase && + !!self.extended == !!other.extended + end + + protected + def options + Options.new(multiline, ignorecase, extended) + end + + def multiline=(multiline) + @multiline = multiline + end + + def ignorecase=(ignorecase) + if @ignorecase.nil? + @array.map! { |e| e.dup(:ignorecase => ignorecase) } + @ignorecase = ignorecase + end + end + + def extended=(extended) + @extended = extended + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/group.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/group.rb new file mode 100644 index 00000000000..d682148bd97 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/group.rb @@ -0,0 +1,90 @@ +module Regin + class Group + attr_reader :expression, :quantifier, :capture, :index, :name + + def initialize(expression, options = {}) + @quantifier = @index = @name = nil + @capture = true + @expression = expression.dup(options) + + @quantifier = options[:quantifier] if options.key?(:quantifier) + @capture = options[:capture] if options.key?(:capture) + @index = options[:index] if options.key?(:index) + @name = options[:name] if options.key?(:name) + end + + def option_names + %w( quantifier capture index name ) + end + + # Returns true if expression could be treated as a literal string. + # + # A Group is literal if its expression is literal and it has no quantifier. + def literal? + quantifier.nil? && expression.literal? + end + + def to_s(parent = false) + if !expression.options? + "(#{capture ? '' : '?:'}#{expression.to_s(parent)})#{quantifier}" + elsif capture == false + "#{expression.to_s}#{quantifier}" + else + "(#{expression.to_s})#{quantifier}" + end + end + + def to_regexp(anchored = false) + re = to_s + re = "\\A#{re}\\Z" if anchored + Regexp.compile(re) + end + + def dup(options = {}) + original_options = option_names.inject({}) do |h, m| + h[m.to_sym] = send(m) + h + end + self.class.new(expression, original_options.merge(options)) + end + + def inspect #:nodoc: + to_s.inspect + end + + def match(char) + to_regexp.match(char) + end + + def include?(char) + expression.include?(char) + end + + def capture? + capture + end + + def ==(other) #:nodoc: + case other + when String + other == to_s + else + eql?(other) + end + end + + def eql?(other) #:nodoc: + other.is_a?(self.class) && + self.expression == other.expression && + self.quantifier == other.quantifier && + self.capture == other.capture && + self.index == other.index && + self.name == other.name + end + + def freeze #:nodoc: + expression.freeze if expression + super + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/options.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/options.rb new file mode 100644 index 00000000000..03ba29d9a53 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/options.rb @@ -0,0 +1,55 @@ +module Regin + class Options + def self.from_int(flags) + multiline = flags & Regexp::MULTILINE != 0 + ignorecase = flags & Regexp::IGNORECASE != 0 + extended = flags & Regexp::EXTENDED != 0 + + new(multiline, ignorecase, extended) + end + + attr_reader :multiline, :ignorecase, :extended + + def initialize(*args) + if args.first.is_a?(Hash) + @multiline = args[0][:multiline] + @ignorecase = args[0][:ignorecase] + @extended = args[0][:extended] + else + @multiline = args[0] + @ignorecase = args[1] + @extended = args[2] + end + end + + def any?(explicit = false) + if explicit + !multiline.nil? || !ignorecase.nil? || !extended.nil? + else + multiline || ignorecase || extended + end + end + + def to_h(explicit = false) + if explicit + options = {} + options[:multiline] = multiline unless multiline.nil? + options[:ignorecase] = ignorecase unless ignorecase.nil? + options[:extended] = extended unless extended.nil? + options + else + { :multiline => multiline, + :ignorecase => ignorecase, + :extended => extended } + end + end + + def to_i + flag = 0 + flag |= Regexp::MULTILINE if multiline + flag |= Regexp::IGNORECASE if ignorecase + flag |= Regexp::EXTENDED if extended + flag + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/parser.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/parser.rb new file mode 100644 index 00000000000..0bb9b87e9c7 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/parser.rb @@ -0,0 +1,415 @@ +# +# DO NOT MODIFY!!!! +# This file is automatically generated by Racc 1.4.6 +# from Racc grammer file "". +# + +require 'racc/parser.rb' +module Regin + class Parser < Racc::Parser #:nodoc: all + +def self.parse_regexp(regexp) + options = Options.from_int(regexp.options) + + parser = new + parser.options_stack << options.to_h + + expression = parser.scan_str(regexp.source) + expression = expression.dup(options.to_h) if options.any? + expression +end + +attr_accessor :options_stack + +def initialize + @capture_index = 0 + @capture_index_stack = [] + @options_stack = [] +end + +##### State transition tables begin ### + +racc_action_table = [ + 2, 18, 19, 19, 8, 10, 11, 13, 48, 19, + 2, 45, 3, 5, 8, 10, 11, 13, 64, 47, + 2, 55, 3, 5, 8, 10, 11, 13, 29, 19, + 2, 16, 3, 5, 8, 10, 11, 13, 61, 19, + 2, 63, 3, 5, 8, 10, 11, 13, 60, 36, + 2, 34, 3, 5, 8, 10, 11, 13, 28, 49, + 2, nil, 3, 5, 8, 10, 11, 13, nil, nil, + 2, nil, 3, 5, 8, 10, 11, 13, nil, 26, + 42, 43, 3, 5, 37, 38, 40, 21, 44, 37, + 38, 40, 22, 23, 24, 14, nil, 15, 31, 32, + 16, nil, 33, 46, 32, nil, nil, 33, 51, 37, + 38, 40, 58, 37, 38, 40, 37, 38, 40, 37, + 38, 40, 37, 38, 40, 37, 38, 40, 37, 38, + 40 ] + +racc_action_check = [ + 0, 4, 27, 4, 0, 0, 0, 0, 36, 56, + 49, 27, 0, 0, 49, 49, 49, 49, 56, 36, + 43, 48, 49, 49, 43, 43, 43, 43, 15, 53, + 6, 15, 43, 43, 6, 6, 6, 6, 53, 52, + 42, 55, 6, 6, 42, 42, 42, 42, 52, 24, + 35, 18, 42, 42, 35, 35, 35, 35, 14, 39, + 19, nil, 35, 35, 19, 19, 19, 19, nil, nil, + 13, nil, 19, 19, 13, 13, 13, 13, nil, 13, + 26, 26, 13, 13, 54, 54, 54, 9, 26, 26, + 26, 26, 9, 9, 9, 2, nil, 2, 17, 17, + 2, nil, 17, 30, 30, nil, nil, 30, 41, 41, + 41, 41, 50, 50, 50, 50, 44, 44, 44, 59, + 59, 59, 58, 58, 58, 51, 51, 51, 62, 62, + 62 ] + +racc_action_pointer = [ + -3, nil, 91, nil, 1, nil, 27, nil, nil, 75, + nil, nil, nil, 67, 53, 22, nil, 93, 51, 57, + nil, nil, nil, nil, 40, nil, 67, 0, nil, nil, + 98, nil, nil, nil, nil, 47, -1, nil, nil, 46, + nil, 87, 37, 17, 94, nil, nil, nil, 12, 7, + 91, 103, 37, 27, 62, 21, 7, nil, 100, 97, + nil, nil, 106, nil, nil, nil, nil, nil ] + +racc_action_default = [ + -37, -13, -37, -19, -37, -20, -2, -4, -11, -6, + -12, -14, -7, -37, -37, -29, -28, -37, -37, -37, + -3, -23, -21, -22, -37, -5, -37, -37, -8, -29, + -37, -9, -27, -26, 68, -1, -37, -34, -35, -37, + -36, -37, -37, -37, -37, -15, -10, -25, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -33, -37, -37, + -17, -18, -37, -24, -16, -32, -31, -30 ] + +racc_goto_table = [ + 4, 41, 20, 35, 17, 39, 25, nil, nil, nil, + nil, nil, nil, 27, nil, nil, 50, 30, nil, 54, + nil, nil, nil, nil, nil, 57, 59, nil, nil, 62, + nil, 20, nil, 65, 66, nil, nil, 67, nil, nil, + nil, nil, 52, 53, nil, nil, nil, nil, nil, 56 ] + +racc_goto_check = [ + 1, 10, 3, 2, 7, 9, 5, nil, nil, nil, + nil, nil, nil, 1, nil, nil, 10, 7, nil, 10, + nil, nil, nil, nil, nil, 10, 10, nil, nil, 10, + nil, 3, nil, 10, 10, nil, nil, 10, nil, nil, + nil, nil, 1, 1, nil, nil, nil, nil, nil, 1 ] + +racc_goto_pointer = [ + nil, 0, -16, -4, nil, -3, nil, 2, nil, -21, + -25 ] + +racc_goto_default = [ + nil, nil, 6, 7, 9, nil, 12, nil, 1, nil, + nil ] + +racc_reduce_table = [ + 0, 0, :racc_error, + 3, 26, :_reduce_1, + 1, 26, :_reduce_2, + 2, 27, :_reduce_3, + 1, 27, :_reduce_4, + 2, 28, :_reduce_5, + 1, 28, :_reduce_none, + 1, 29, :_reduce_none, + 3, 29, :_reduce_8, + 3, 29, :_reduce_9, + 4, 29, :_reduce_10, + 1, 29, :_reduce_11, + 1, 29, :_reduce_12, + 1, 29, :_reduce_13, + 1, 29, :_reduce_14, + 3, 31, :_reduce_15, + 6, 31, :_reduce_16, + 5, 31, :_reduce_17, + 5, 31, :_reduce_18, + 1, 33, :_reduce_none, + 1, 33, :_reduce_none, + 1, 30, :_reduce_none, + 1, 30, :_reduce_none, + 1, 30, :_reduce_none, + 5, 30, :_reduce_24, + 3, 30, :_reduce_25, + 2, 32, :_reduce_26, + 2, 32, :_reduce_27, + 1, 32, :_reduce_none, + 1, 32, :_reduce_none, + 4, 34, :_reduce_30, + 4, 34, :_reduce_31, + 4, 34, :_reduce_32, + 3, 34, :_reduce_33, + 1, 35, :_reduce_34, + 1, 35, :_reduce_35, + 1, 35, :_reduce_36 ] + +racc_reduce_n = 37 + +racc_shift_n = 68 + +racc_token_table = { + false => 0, + :error => 1, + :BAR => 2, + :LBRACK => 3, + :CTYPE => 4, + :RBRACK => 5, + :NEGATE => 6, + :CCLASS => 7, + :DOT => 8, + :CHAR => 9, + :LPAREN => 10, + :RPAREN => 11, + :QMARK => 12, + :COLON => 13, + :NAME => 14, + :L_ANCHOR => 15, + :R_ANCHOR => 16, + :STAR => 17, + :PLUS => 18, + :LCURLY => 19, + :RCURLY => 20, + :MINUS => 21, + :MULTILINE => 22, + :IGNORECASE => 23, + :EXTENDED => 24 } + +racc_nt_base = 25 + +racc_use_result_var = true + +Racc_arg = [ + racc_action_table, + racc_action_check, + racc_action_default, + racc_action_pointer, + racc_goto_table, + racc_goto_check, + racc_goto_default, + racc_goto_pointer, + racc_nt_base, + racc_reduce_table, + racc_token_table, + racc_shift_n, + racc_reduce_n, + racc_use_result_var ] + +Racc_token_to_s_table = [ + "$end", + "error", + "BAR", + "LBRACK", + "CTYPE", + "RBRACK", + "NEGATE", + "CCLASS", + "DOT", + "CHAR", + "LPAREN", + "RPAREN", + "QMARK", + "COLON", + "NAME", + "L_ANCHOR", + "R_ANCHOR", + "STAR", + "PLUS", + "LCURLY", + "RCURLY", + "MINUS", + "MULTILINE", + "IGNORECASE", + "EXTENDED", + "$start", + "expression", + "subexpression", + "quantified_atom", + "atom", + "quantifier", + "group", + "bracket_expression", + "anchor", + "options", + "modifier" ] + +Racc_debug_parser = false + +##### State transition tables end ##### + +# reduce 0 omitted + +def _reduce_1(val, _values, result) + # TODO remove this conditional by breaking + # it into another production + if val[0][0].is_a?(Regin::Alternation) + alt = val[0][0] + [Expression.new(val[2])] + else + alt = Alternation.new(val[0], Expression.new(val[2])) + end + result = Expression.new(alt) + + result +end + +def _reduce_2(val, _values, result) + result = Expression.new(val[0]) + result +end + +def _reduce_3(val, _values, result) + result = val[0] + [val[1]] + result +end + +def _reduce_4(val, _values, result) + result = [val[0]] + result +end + +def _reduce_5(val, _values, result) + result = val[0].dup(:quantifier => val[1]) + result +end + +# reduce 6 omitted + +# reduce 7 omitted + +def _reduce_8(val, _values, result) + result = CharacterClass.new(val[1]) + result +end + +def _reduce_9(val, _values, result) + result = CharacterClass.new(val[1]) + result +end + +def _reduce_10(val, _values, result) + result = CharacterClass.new(val[2], :negate => true) + result +end + +def _reduce_11(val, _values, result) + result = CharacterClass.new(val[0]) + result +end + +def _reduce_12(val, _values, result) + result = CharacterClass.new('.') + result +end + +def _reduce_13(val, _values, result) + result = Anchor.new(val[0]) + result +end + +def _reduce_14(val, _values, result) + result = Character.new(val[0]) + result +end + +def _reduce_15(val, _values, result) + result = Group.new(val[1], :index => @capture_index_stack.pop) + + result +end + +def _reduce_16(val, _values, result) + result = Group.new(val[4], val[2].merge(:capture => false)) + @options_stack.pop + + result +end + +def _reduce_17(val, _values, result) + result = Group.new(val[3], :capture => false); + + result +end + +def _reduce_18(val, _values, result) + result = Group.new(val[3], :name => val[2], :index => @capture_index_stack.pop); + + result +end + +# reduce 19 omitted + +# reduce 20 omitted + +# reduce 21 omitted + +# reduce 22 omitted + +# reduce 23 omitted + +def _reduce_24(val, _values, result) + result = val.join + result +end + +def _reduce_25(val, _values, result) + result = val.join + result +end + +def _reduce_26(val, _values, result) + result = val.join + result +end + +def _reduce_27(val, _values, result) + result = val.join + result +end + +# reduce 28 omitted + +# reduce 29 omitted + +def _reduce_30(val, _values, result) + @options_stack << result = { val[1] => false, val[2] => false, val[3] => false } + + result +end + +def _reduce_31(val, _values, result) + @options_stack << result = { val[0] => true, val[2] => false, val[3] => false } + + result +end + +def _reduce_32(val, _values, result) + @options_stack << result = { val[0] => true, val[1] => true, val[3] => false } + + result +end + +def _reduce_33(val, _values, result) + @options_stack << result = { val[0] => true, val[1] => true, val[2] => true } + + result +end + +def _reduce_34(val, _values, result) + result = :multiline + result +end + +def _reduce_35(val, _values, result) + result = :ignorecase + result +end + +def _reduce_36(val, _values, result) + result = :extended + result +end + +def _reduce_none(val, _values, result) + val[0] +end + + end # class Parser +end # module Regin + +require 'regin/tokenizer' diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/tokenizer.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/tokenizer.rb new file mode 100644 index 00000000000..59e4ffb6110 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/tokenizer.rb @@ -0,0 +1,213 @@ +#-- +# DO NOT MODIFY!!!! +# This file is automatically generated by rex 1.0.5.beta1 +# from lexical definition file "lib/regin/tokenizer.rex". +#++ + +require 'racc/parser' +class Regin::Parser < Racc::Parser + require 'strscan' + + class ScanError < StandardError ; end + + attr_reader :lineno + attr_reader :filename + attr_accessor :state + + def scan_setup(str) + @ss = StringScanner.new(str) + @lineno = 1 + @state = nil + end + + def action + yield + end + + def scan_str(str) + scan_setup(str) + do_parse + end + alias :scan :scan_str + + def load_file( filename ) + @filename = filename + open(filename, "r") do |f| + scan_setup(f.read) + end + end + + def scan_file( filename ) + load_file(filename) + do_parse + end + + + def next_token + return if @ss.eos? + + text = @ss.peek(1) + @lineno += 1 if text == "\n" + token = case @state + when nil + case + when (text = @ss.scan(/\\[dDsSwW]/)) + action { [:CCLASS, text] } + + when (text = @ss.scan(/\^|\\A/)) + action { [:L_ANCHOR, text] } + + when (text = @ss.scan(/\$|\\Z/)) + action { [:R_ANCHOR, text] } + + when (text = @ss.scan(/<(\w+)>/)) + action { [:NAME, @ss[1]] } + + when (text = @ss.scan(/\(/)) + action { + @capture_index_stack << @capture_index + @capture_index += 1 + @state = :OPTIONS if @ss.peek(1) == '?'; + [:LPAREN, text] + } + + + when (text = @ss.scan(/\)/)) + action { [:RPAREN, text] } + + when (text = @ss.scan(/\[/)) + action { @state = :CCLASS; [:LBRACK, text] } + + when (text = @ss.scan(/\{/)) + action { [:LCURLY, text] } + + when (text = @ss.scan(/\}/)) + action { [:RCURLY, text] } + + when (text = @ss.scan(/\|/)) + action { [:BAR, text] } + + when (text = @ss.scan(/\./)) + action { [:DOT, text] } + + when (text = @ss.scan(/\?/)) + action { [:QMARK, text] } + + when (text = @ss.scan(/\+(?:\?)/)) + action { [:PLUS, text] } + + when (text = @ss.scan(/\*(?:\?)/)) + action { [:STAR, text] } + + when (text = @ss.scan(/\#/)) + action { + if @options_stack[-1][:extended] + @state = :COMMENT; + next_token + else + [:CHAR, text] + end + } + + + when (text = @ss.scan(/\s|\n/)) + action { + if @options_stack[-1][:extended] + next_token + else + [:CHAR, text] + end + } + + + when (text = @ss.scan(/\\(.)/)) + action { [:CHAR, @ss[1]] } + + when (text = @ss.scan(/./)) + action { [:CHAR, text] } + + else + text = @ss.string[@ss.pos .. -1] + raise ScanError, "can not match: '" + text + "'" + end # if + + when :CCLASS + case + when (text = @ss.scan(/\]/)) + action { @state = nil; [:RBRACK, text] } + + when (text = @ss.scan(/\^/)) + action { [:NEGATE, text] } + + when (text = @ss.scan(/:(alnum|alpha|ascii|blank|cntrl|digit|graph|lower|print|punct|space|upper|word|xdigit):/)) + action { [:CTYPE, text] } + + when (text = @ss.scan(/\\-/)) + action { [:CHAR, text] } + + when (text = @ss.scan(/\\(.)/)) + action { [:CHAR, @ss[1]] } + + when (text = @ss.scan(/./)) + action { [:CHAR, text] } + + else + text = @ss.string[@ss.pos .. -1] + raise ScanError, "can not match: '" + text + "'" + end # if + + when :OPTIONS + case + when (text = @ss.scan(/\?/)) + action { + @state = nil unless @ss.peek(1) =~ /-|m|i|x|:/ + [:QMARK, text] + } + + + when (text = @ss.scan(/\-/)) + action { [:MINUS, text] } + + when (text = @ss.scan(/m/)) + action { [:MULTILINE, text] } + + when (text = @ss.scan(/i/)) + action { [:IGNORECASE, text] } + + when (text = @ss.scan(/x/)) + action { [:EXTENDED, text] } + + when (text = @ss.scan(/\:/)) + action { + @capture_index_stack.pop + @capture_index -= 1 + @state = nil; + [:COLON, text] + } + + + else + text = @ss.string[@ss.pos .. -1] + raise ScanError, "can not match: '" + text + "'" + end # if + + when :COMMENT + case + when (text = @ss.scan(/\n/)) + action { @state = nil; next_token } + + when (text = @ss.scan(/./)) + action { next_token } + + else + text = @ss.string[@ss.pos .. -1] + raise ScanError, "can not match: '" + text + "'" + end # if + + else + raise ScanError, "undefined state: '" + state.to_s + "'" + end # case state + token + end # def next_token + +end # class diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/version.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/version.rb new file mode 100644 index 00000000000..7ad2a5a25eb --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/version.rb @@ -0,0 +1,3 @@ +module Regin + Version = '0.3.3' +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/version.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/version.rb new file mode 100644 index 00000000000..a3688b102ec --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/version.rb @@ -0,0 +1,5 @@ +module Rack + module Mount + Version = '0.6.6.pre' + end +end diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index ea1bf14c96a..f9105ca3644 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -178,9 +178,7 @@ module ActionView def flush_output_buffer #:nodoc: if output_buffer && !output_buffer.empty? response.body_parts << output_buffer - new = '' - new.force_encoding(output_buffer.encoding) if new.respond_to?(:force_encoding) - self.output_buffer = new + self.output_buffer = output_buffer[0,0] nil end end diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index ea491b2db82..4c1b751160e 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -529,23 +529,34 @@ module ActionView def html_options_for_form(url_for_options, options, *parameters_for_url) returning options.stringify_keys do |html_options| html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart") + # The following URL is unescaped, this is just a hash of options, and it is the + # responsability of the caller to escape all the values. html_options["action"] = url_for(url_for_options, *parameters_for_url) + html_options["accept-charset"] = "UTF-8" html_options["data-remote"] = true if html_options.delete("remote") end end def extra_tags_for_form(html_options) - case method = html_options.delete("method").to_s + snowman_tag = tag(:input, :type => "hidden", + :name => "_snowman", :value => "☃".html_safe) + + method = html_options.delete("method").to_s + + method_tag = case method when /^get$/i # must be case-insensitive, but can't use downcase as might be nil html_options["method"] = "get" '' when /^post$/i, "", nil html_options["method"] = "post" - protect_against_forgery? ? content_tag(:div, token_tag, :style => 'margin:0;padding:0;display:inline') : '' + token_tag else html_options["method"] = "post" - content_tag(:div, tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag, :style => 'margin:0;padding:0;display:inline') + tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag end + + tags = snowman_tag << method_tag + content_tag(:div, tags, :style => 'margin:0;padding:0;display:inline') end def form_tag_html(html_options) diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index bfb8f74a003..5c0ff5d59cb 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -1,6 +1,5 @@ require 'set' require 'active_support/json' -require 'active_support/core_ext/object/returning' require 'active_support/core_ext/object/blank' module ActionView @@ -132,7 +131,7 @@ module ActionView url_options = options[:url] url_options = url_options.merge(:escape => false) if url_options.is_a?(Hash) - function << "'#{escape_javascript(url_for(url_options))}'" + function << "'#{html_escape(escape_javascript(url_for(url_options)))}'" function << ", #{javascript_options})" function = "#{options[:before]}; #{function}" if options[:before] @@ -228,7 +227,7 @@ module ActionView # ".html_safe @@ -519,9 +511,9 @@ module ActionView char = c.chr string << (char =~ /\w/ ? sprintf("%%%x", c) : char) end - content_tag "a", name || email_address_encoded.html_safe, html_options.merge({ "href" => "#{string}#{extras}" }) + content_tag "a", name || email_address_encoded.html_safe, html_options.merge("href" => "#{string}#{extras}".html_safe) else - content_tag "a", name || email_address_obfuscated.html_safe, html_options.merge({ "href" => "mailto:#{email_address}#{extras}" }) + content_tag "a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe) end end @@ -574,7 +566,7 @@ module ActionView "in a #request method" end - url_string = CGI.unescapeHTML(url_for(options)) + url_string = url_for(options) # We ignore any extra parameters in the request_uri if the # submitted url doesn't have any either. This lets the function diff --git a/actionpack/lib/action_view/log_subscriber.rb b/actionpack/lib/action_view/log_subscriber.rb index 4a52937c580..443a0eafd16 100644 --- a/actionpack/lib/action_view/log_subscriber.rb +++ b/actionpack/lib/action_view/log_subscriber.rb @@ -12,8 +12,9 @@ module ActionView alias :render_partial :render_template alias :render_collection :render_template + # TODO: Ideally, ActionView should have its own logger so it does not depend on AC.logger def logger - ActionController::Base.logger + ActionController::Base.logger if defined?(ActionController::Base) end protected @@ -24,4 +25,4 @@ module ActionView end end -ActionView::LogSubscriber.attach_to :action_view \ No newline at end of file +ActionView::LogSubscriber.attach_to :action_view diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index c00557209be..40ff1f21824 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -156,11 +156,12 @@ module ActionView end def inspect - if defined?(Rails.root) - identifier.sub("#{Rails.root}/", '') - else - identifier - end + @inspect ||= + if defined?(Rails.root) + identifier.sub("#{Rails.root}/", '') + else + identifier + end end private @@ -267,9 +268,11 @@ module ActionView end def build_method_name(locals) - # TODO: is locals.keys.hash reliably the same? - @method_names[locals.keys.hash] ||= - "_render_template_#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_") + @method_names[locals.keys.hash] ||= "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_") + end + + def identifier_method_name + @identifier_method_name ||= inspect.gsub(/[^a-z_]/, '_') end end end diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index 757e4cf77cc..e5614c9e3bb 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -181,7 +181,7 @@ module ActionView } def _instance_variables - instance_variables - EXCLUDE_IVARS + instance_variables.map(&:to_s) - EXCLUDE_IVARS end def _assigns diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 2640e964535..c8477fb83fa 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -38,6 +38,17 @@ end require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late +module Rails +end + +# Monkey patch the old router initialization to be silenced. +class ActionDispatch::Routing::DeprecatedMapper + def initialize_with_silencer(*args) + ActiveSupport::Deprecation.silence { initialize_without_silencer(*args) } + end + alias_method_chain :initialize, :silencer +end + ActiveSupport::Dependencies.hook! # Show backtraces for deprecated behavior for quicker cleanup. diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb index 6e1e6cdd201..90a1ef982c3 100644 --- a/actionpack/test/activerecord/polymorphic_routes_test.rb +++ b/actionpack/test/activerecord/polymorphic_routes_test.rb @@ -408,18 +408,18 @@ class PolymorphicRoutesTest < ActionController::TestCase def with_admin_test_routes(options = {}) with_routing do |set| - set.draw do |map| - map.namespace :admin do |admin| - admin.resources :projects do |projects| - projects.resources :tasks - projects.resource :bid do |bid| - bid.resources :tasks + set.draw do + namespace :admin do + resources :projects do + resources :tasks + resource :bid do + resources :tasks end end - admin.resources :taxes do |taxes| - taxes.resources :faxes + resources :taxes do + resources :faxes end - admin.resources :series + resources :series end end @@ -430,12 +430,12 @@ class PolymorphicRoutesTest < ActionController::TestCase def with_admin_and_site_test_routes(options = {}) with_routing do |set| - set.draw do |map| - map.namespace :admin do |admin| - admin.resources :projects do |projects| - projects.namespace :site do |site| - site.resources :tasks do |tasks| - tasks.resources :steps + set.draw do + namespace :admin do + resources :projects do + namespace :site do + resources :tasks do + resources :steps end end end diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index 4f58b5d9687..ae270b751ec 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -2,9 +2,6 @@ require 'abstract_unit' require 'logger' require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late -module Rails -end - # Provide some controller to run the tests on. module Submodule class ContainedEmptyController < ActionController::Base diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb index 40c5ac2d09a..e3ec5cf1821 100644 --- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb @@ -89,15 +89,21 @@ class MultipartParamsParsingTest < ActionController::IntegrationTest assert_equal 19512, file.size end + # Pending fix in Rack 1.2.2 + # http://rack.lighthouseapp.com/projects/22435-rack/tickets/79-multipart-handling-incorrectly-assuming-file-upload test "parses mixed files" do - params = parse_multipart('mixed_files') - assert_equal %w(files foo), params.keys.sort - assert_equal 'bar', params['foo'] + if Rack.release <= '1.2.1' + $stderr.puts 'multipart/mixed parsing pending fix in Rack 1.2.2' + else + params = parse_multipart('mixed_files') + assert_equal %w(files foo), params.keys.sort + assert_equal 'bar', params['foo'] - # Rack doesn't handle multipart/mixed for us. - files = params['files'] - files.force_encoding('ASCII-8BIT') if files.respond_to?(:force_encoding) - assert_equal 19756, files.size + # Rack doesn't handle multipart/mixed for us. + files = params['files'] + files.force_encoding('ASCII-8BIT') if files.respond_to?(:force_encoding) + assert_equal 19756, files.size + end end test "does not create tempfile if no file has been selected" do diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb index 69dbd7f5281..0bcef815346 100644 --- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb @@ -141,6 +141,29 @@ class UrlEncodedParamsParsingTest < ActionController::IntegrationTest post "/parse", actual assert_response :ok assert_equal(expected, TestController.last_request_parameters) + assert_utf8(TestController.last_request_parameters) + end + end + + def assert_utf8(object) + return unless "ruby".encoding_aware? + + correct_encoding = Encoding.default_internal + + unless object.is_a?(Hash) + assert_equal correct_encoding, object.encoding, "#{object.inspect} should have been UTF-8" + return + end + + object.each do |k,v| + case v + when Hash + assert_utf8(v) + when Array + v.each {|el| assert_utf8(el) } + else + assert_utf8(v) + end end end end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index cf92b039e3d..26bd641cd64 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -35,6 +35,13 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + scope "bookmark", :controller => "bookmarks", :as => :bookmark do + get :new, :path => "build" + post :create, :path => "create", :as => "" + put :update + get "remove", :action => :destroy, :as => :remove + end + match 'account/logout' => redirect("/logout"), :as => :logout_redirect match 'account/login', :to => redirect("/login") @@ -330,6 +337,17 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest resources :content + scope :constraints => { :id => /\d+/ } do + get '/tickets', :to => 'tickets#index', :as => :tickets + end + + scope :constraints => { :id => /\d{4}/ } do + resources :movies do + resources :reviews + resource :trailer + end + end + match '/:locale/*file.:format', :to => 'files#show', :file => /path\/to\/existing\/file/ end end @@ -534,6 +552,26 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_bookmarks + with_test_routes do + get '/bookmark/build' + assert_equal 'bookmarks#new', @response.body + assert_equal '/bookmark/build', new_bookmark_path + + post '/bookmark/create' + assert_equal 'bookmarks#create', @response.body + assert_equal '/bookmark/create', bookmark_path + + put '/bookmark' + assert_equal 'bookmarks#update', @response.body + assert_equal '/bookmark', update_bookmark_path + + get '/bookmark/remove' + assert_equal 'bookmarks#destroy', @response.body + assert_equal '/bookmark/remove', bookmark_remove_path + end + end + def test_admin with_test_routes do get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'} @@ -1546,6 +1584,50 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_router_removes_invalid_conditions + with_test_routes do + get '/tickets' + assert_equal 'tickets#index', @response.body + assert_equal '/tickets', tickets_path + end + end + + def test_constraints_are_merged_from_scope + with_test_routes do + get '/movies/0001' + assert_equal 'movies#show', @response.body + assert_equal '/movies/0001', movie_path(:id => '0001') + + get '/movies/00001' + assert_equal 'Not Found', @response.body + assert_raises(ActionController::RoutingError){ movie_path(:id => '00001') } + + get '/movies/0001/reviews' + assert_equal 'reviews#index', @response.body + assert_equal '/movies/0001/reviews', movie_reviews_path(:movie_id => '0001') + + get '/movies/00001/reviews' + assert_equal 'Not Found', @response.body + assert_raises(ActionController::RoutingError){ movie_reviews_path(:movie_id => '00001') } + + get '/movies/0001/reviews/0001' + assert_equal 'reviews#show', @response.body + assert_equal '/movies/0001/reviews/0001', movie_review_path(:movie_id => '0001', :id => '0001') + + get '/movies/00001/reviews/0001' + assert_equal 'Not Found', @response.body + assert_raises(ActionController::RoutingError){ movie_path(:movie_id => '00001', :id => '00001') } + + get '/movies/0001/trailer' + assert_equal 'trailers#show', @response.body + assert_equal '/movies/0001/trailer', movie_trailer_path(:movie_id => '0001') + + get '/movies/00001/trailer' + assert_equal 'Not Found', @response.body + assert_raises(ActionController::RoutingError){ movie_trailer_path(:movie_id => '00001') } + end + end + private def with_test_routes yield diff --git a/actionpack/test/template/erb/form_for_test.rb b/actionpack/test/template/erb/form_for_test.rb index ec6e8727356..e722b40a9a9 100644 --- a/actionpack/test/template/erb/form_for_test.rb +++ b/actionpack/test/template/erb/form_for_test.rb @@ -5,7 +5,7 @@ module ERBTest class TagHelperTest < BlockTestCase test "form_for works" do output = render_content "form_for(:staticpage, :url => {:controller => 'blah', :action => 'update'})", "" - assert_equal "
", output + assert_match %r{.*}, output end end end diff --git a/actionpack/test/template/erb/tag_helper_test.rb b/actionpack/test/template/erb/tag_helper_test.rb index 64a88bde1d1..d0731009864 100644 --- a/actionpack/test/template/erb/tag_helper_test.rb +++ b/actionpack/test/template/erb/tag_helper_test.rb @@ -28,8 +28,8 @@ module ERBTest end test "percent equals works with form tags" do - expected_output = "
hello
" - maybe_deprecated { assert_equal expected_output, render_content("form_tag('foo')", "<%= 'hello' %>") } + expected_output = %r{.*hello*} + maybe_deprecated { assert_match expected_output, render_content("form_tag('foo')", "<%= 'hello' %>") } end test "percent equals works with fieldset tags" do diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 6ba407e2300..4b9e41803f7 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -583,7 +583,8 @@ class FormHelperTest < ActionView::TestCase end expected = - "
" + + "" + + snowman + "" + "" + "" + @@ -604,15 +605,14 @@ class FormHelperTest < ActionView::TestCase concat f.submit('Create post') end - expected = - "" + - "
" + + expected = whole_form("/posts/123", "create-post", "other_name_edit", :method => "put") do "" + "" + "" + "" + "" + - "
" + "" + end assert_dom_equal expected, output_buffer end @@ -626,14 +626,12 @@ class FormHelperTest < ActionView::TestCase end end - expected = - "
" + - "
" + + expected = whole_form("http://www.example.com", "create-post", nil, "put") do "" + "" + "" + - "" + - "
" + "" + end assert_dom_equal expected, output_buffer end @@ -647,14 +645,12 @@ class FormHelperTest < ActionView::TestCase end end - expected = - "
" + - "
" + + expected = whole_form("http://www.example.com", "create-post", nil, :method => "put", :remote => true) do "" + "" + "" + - "" + - "
" + "" + end assert_dom_equal expected, output_buffer end @@ -668,13 +664,12 @@ class FormHelperTest < ActionView::TestCase end end - expected = - "
" + + expected = whole_form("http://www.example.com", nil, nil, :remote => true) do "" + "" + "" + - "" + - "
" + "" + end assert_dom_equal expected, output_buffer end @@ -686,13 +681,12 @@ class FormHelperTest < ActionView::TestCase concat f.check_box(:secret) end - expected = - "
" + + expected = whole_form("http://www.example.com", "create-post") do "" + "" + "" + - "" + - "
" + "" + end assert_dom_equal expected, output_buffer end @@ -707,14 +701,13 @@ class FormHelperTest < ActionView::TestCase end end - expected = - "
" + + expected = whole_form do "" + "" + "" + "" + - "" + - "
" + "" + end assert_dom_equal expected, output_buffer end @@ -728,13 +721,12 @@ class FormHelperTest < ActionView::TestCase end end - expected = - "
" + + expected = whole_form do "" + "" + "" + - "" + - "
" + "" + end assert_dom_equal expected, output_buffer end @@ -749,9 +741,10 @@ class FormHelperTest < ActionView::TestCase end end - expected = "
" + - "" + - "
" + expected = whole_form do + "" + end + assert_dom_equal expected, output_buffer ensure I18n.locale = old_locale @@ -766,9 +759,10 @@ class FormHelperTest < ActionView::TestCase end end - expected = "
" + - "" + - "
" + expected = whole_form do + "" + end + assert_dom_equal expected, output_buffer ensure I18n.locale = old_locale @@ -781,9 +775,10 @@ class FormHelperTest < ActionView::TestCase concat f.submit :class => "extra" end - expected = "
" + - "" + - "
" + expected = whole_form do + "" + end + assert_dom_equal expected, output_buffer ensure I18n.locale = old_locale @@ -798,9 +793,10 @@ class FormHelperTest < ActionView::TestCase end end - expected = "
" + - "" + - "
" + expected = whole_form do + "" + end + assert_dom_equal expected, output_buffer ensure I18n.locale = old_locale @@ -815,9 +811,9 @@ class FormHelperTest < ActionView::TestCase end end - expected = "
" + - "" + - "
" + expected = whole_form do + "" + end assert_dom_equal expected, output_buffer end @@ -832,10 +828,10 @@ class FormHelperTest < ActionView::TestCase end end - expected = "
" + - "" + - "" + - "
" + expected = whole_form do + "" + + "" + end assert_dom_equal expected, output_buffer end @@ -850,10 +846,10 @@ class FormHelperTest < ActionView::TestCase end end - expected = "
" + - "" + - "" + - "
" + expected = whole_form do + "" + + "" + end assert_dom_equal expected, output_buffer end @@ -867,9 +863,9 @@ class FormHelperTest < ActionView::TestCase end end - expected = "
" + - "" + - "
" + expected = whole_form do + "" + end assert_dom_equal expected, output_buffer end @@ -883,9 +879,9 @@ class FormHelperTest < ActionView::TestCase end end - expected = "
" + - "" + - "
" + expected = whole_form do + "" + end assert_dom_equal expected, output_buffer end @@ -899,9 +895,9 @@ class FormHelperTest < ActionView::TestCase end end - expected = "
" + - "" + - "
" + expected = whole_form do + "" + end assert_dom_equal expected, output_buffer end @@ -915,9 +911,9 @@ class FormHelperTest < ActionView::TestCase end end - expected = "
" + - "" + - "
" + expected = whole_form do + "" + end assert_dom_equal expected, output_buffer end @@ -931,9 +927,9 @@ class FormHelperTest < ActionView::TestCase end end - expected = "
" + - "" + - "
" + expected = whole_form do + "" + end assert_dom_equal expected, output_buffer end @@ -952,12 +948,11 @@ class FormHelperTest < ActionView::TestCase } end - expected = "
" + - "" + - "
" + - "
" + - "" + - "
" + expected = whole_form do + "" + end + whole_form do + "" + end assert_dom_equal expected, output_buffer end @@ -975,10 +970,10 @@ class FormHelperTest < ActionView::TestCase end end - expected = '
' + - '' + - '' + - '
' + expected = whole_form do + '' + + '' + end assert_dom_equal expected, output_buffer end @@ -1006,11 +1001,11 @@ class FormHelperTest < ActionView::TestCase end end - expected = '
' + - '' + - '' + - '' + - '
' + expected = whole_form do + '' + + '' + + '' + end assert_dom_equal expected, output_buffer end @@ -1028,11 +1023,11 @@ class FormHelperTest < ActionView::TestCase end end - expected = '
' + - '' + - '' + - '' + - '
' + expected = whole_form do + '' + + '' + + '' + end assert_dom_equal expected, output_buffer end @@ -1051,13 +1046,13 @@ class FormHelperTest < ActionView::TestCase end end - expected = '
' + - '' + - '' + - '' + - '' + - '' + - '
' + expected = whole_form do + '' + + '' + + '' + + '' + + '' + end assert_dom_equal expected, output_buffer end @@ -1077,13 +1072,13 @@ class FormHelperTest < ActionView::TestCase end end - expected = '
' + - '' + - '' + - '' + - '' + - '' + - '
' + expected = whole_form do + '' + + '' + + '' + + '' + + '' + end assert_dom_equal expected, output_buffer end @@ -1102,11 +1097,11 @@ class FormHelperTest < ActionView::TestCase end end - expected = '
' + - '' + - '' + - '' + - '
' + expected = whole_form do + '' + + '' + + '' + end assert_dom_equal expected, output_buffer end @@ -1125,12 +1120,12 @@ class FormHelperTest < ActionView::TestCase end end - expected = '
' + - '' + - '' + - '' + - '' + - '
' + expected = whole_form do + '' + + '' + + '' + + '' + end assert_dom_equal expected, output_buffer end @@ -1145,9 +1140,9 @@ class FormHelperTest < ActionView::TestCase end end - expected = '
' + - '' + - '
' + expected = whole_form do + '' + end assert_dom_equal expected, output_buffer end @@ -1164,13 +1159,13 @@ class FormHelperTest < ActionView::TestCase end end - expected = '
' + - '' + - '' + - '' + - '' + - '' + - '
' + expected = whole_form do + '' + + '' + + '' + + '' + + '' + end assert_dom_equal expected, output_buffer end @@ -1188,13 +1183,13 @@ class FormHelperTest < ActionView::TestCase end end - expected = '
' + - '' + - '' + - '' + - '' + - '' + - '
' + expected = whole_form do + '' + + '' + + '' + + '' + + '' + end assert_dom_equal expected, output_buffer end @@ -1213,12 +1208,12 @@ class FormHelperTest < ActionView::TestCase end end - expected = '
' + - '' + - '' + - '' + - '' + - '
' + expected = whole_form do + '' + + '' + + '' + + '' + end assert_dom_equal expected, output_buffer assert_equal yielded_comments, @post.comments @@ -1235,10 +1230,10 @@ class FormHelperTest < ActionView::TestCase end end - expected = '
' + - '' + - '' + - '
' + expected = whole_form do + '' + + '' + end assert_dom_equal expected, output_buffer end @@ -1273,20 +1268,20 @@ class FormHelperTest < ActionView::TestCase end end - expected = '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
' + expected = whole_form do + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + end assert_dom_equal expected, output_buffer end @@ -1426,7 +1421,8 @@ class FormHelperTest < ActionView::TestCase end expected = - "
" + + "" + + snowman + "" + "" + "" + @@ -1449,11 +1445,11 @@ class FormHelperTest < ActionView::TestCase end expected = - "" + - "" + - "" + - "" + - "
" + whole_form("http://www.example.com", "create-post") do + "" + + "" + + "" + end assert_dom_equal expected, output_buffer end @@ -1477,16 +1473,42 @@ class FormHelperTest < ActionView::TestCase end end - expected = - "
" + - "
" + - "
" + - "
" + - "
" + expected = whole_form do + "
" + + "
" + + "
" + end assert_dom_equal expected, output_buffer end + def snowman(method = nil) + txt = %{
} + txt << %{} + txt << %{} if method + txt << %{
} + end + + def form_text(action = "http://www.example.com", id = nil, html_class = nil, remote = nil) + txt = %{
} + end + + def whole_form(action = "http://www.example.com", id = nil, html_class = nil, options = nil) + contents = block_given? ? yield : "" + + if options.is_a?(Hash) + method, remote = options.values_at(:method, :remote) + else + method = options + end + + form_text(action, id, html_class, remote) + snowman(method) + contents + "
" + end + def test_default_form_builder old_default_form_builder, ActionView::Base.default_form_builder = ActionView::Base.default_form_builder, LabelledFormBuilder @@ -1499,12 +1521,11 @@ class FormHelperTest < ActionView::TestCase end end - expected = - "
" + + expected = whole_form do "
" + "
" + - "
" + - "
" + "
" + end assert_dom_equal expected, output_buffer ensure @@ -1577,7 +1598,7 @@ class FormHelperTest < ActionView::TestCase assert_deprecated do form_for(:post, @post, :html => {:id => 'some_form', :class => 'some_class'}) do |f| end end - expected = "
" + expected = whole_form("http://www.example.com", "some_form", "some_class") assert_dom_equal expected, output_buffer end @@ -1587,7 +1608,8 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post, :url => 'http://www.otherdomain.com') do |f| end end - assert_equal '
', output_buffer + assert_equal whole_form("http://www.otherdomain.com"), output_buffer + # assert_equal '
', output_buffer end def test_form_for_with_hash_url_option @@ -1604,14 +1626,15 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post, :url => @post) do |f| end end - expected = "
" + expected = whole_form("/posts/123") + # expected = "
" assert_equal expected, output_buffer end def test_form_for_with_existing_object form_for(@post) do |f| end - expected = "
" + expected = whole_form("/posts/123", "edit_post_123", "edit_post", "put") assert_equal expected, output_buffer end @@ -1622,7 +1645,7 @@ class FormHelperTest < ActionView::TestCase form_for(post) do |f| end - expected = "
" + expected = whole_form("/posts", "new_post", "new_post") assert_equal expected, output_buffer end @@ -1630,14 +1653,14 @@ class FormHelperTest < ActionView::TestCase @comment.save form_for([@post, @comment]) {} - expected = %(
) + expected = whole_form(comment_path(@post, @comment), "edit_comment_1", "edit_comment", "put") assert_dom_equal expected, output_buffer end def test_form_for_with_new_object_in_list form_for([@post, @comment]) {} - expected = %(
) + expected = whole_form(comments_path(@post), "new_comment", "new_comment") assert_dom_equal expected, output_buffer end @@ -1645,21 +1668,21 @@ class FormHelperTest < ActionView::TestCase @comment.save form_for([:admin, @post, @comment]) {} - expected = %(
) + expected = whole_form(admin_comment_path(@post, @comment), "edit_comment_1", "edit_comment", "put") assert_dom_equal expected, output_buffer end def test_form_for_with_new_object_and_namespace_in_list form_for([:admin, @post, @comment]) {} - expected = %(
) + expected = whole_form(admin_comments_path(@post), "new_comment", "new_comment") assert_dom_equal expected, output_buffer end def test_form_for_with_existing_object_and_custom_url form_for(@post, :url => "/super_posts") do |f| end - expected = "
" + expected = whole_form("/super_posts", "edit_post_123", "edit_post", "put") assert_equal expected, output_buffer end diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index b75863bb6a6..8a0f352bc02 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -8,6 +8,36 @@ class FormTagHelperTest < ActionView::TestCase @controller = BasicController.new end + def snowman(options = {}) + method = options[:method] + + txt = %{
} + txt << %{} + txt << %{} if method + txt << %{
} + end + + def form_text(action = "http://www.example.com", options = {}) + remote, enctype, html_class, id = options.values_at(:remote, :enctype, :html_class, :id) + + txt = %{
} + end + + def whole_form(action = "http://www.example.com", options = {}) + out = form_text(action, options) + snowman(options) + + if block_given? + out << yield << "
" + end + + out + end + def url_for(options) if options.is_a?(Hash) "http://www.example.com" @@ -31,51 +61,57 @@ class FormTagHelperTest < ActionView::TestCase def test_form_tag actual = form_tag - expected = %(
) + expected = whole_form assert_dom_equal expected, actual end def test_form_tag_multipart actual = form_tag({}, { 'multipart' => true }) - expected = %() + expected = whole_form("http://www.example.com", :enctype => true) assert_dom_equal expected, actual end def test_form_tag_with_method_put actual = form_tag({}, { :method => :put }) - expected = %(
) + expected = whole_form("http://www.example.com", :method => :put) assert_dom_equal expected, actual end def test_form_tag_with_method_delete actual = form_tag({}, { :method => :delete }) - expected = %(
) + + expected = whole_form("http://www.example.com", :method => :delete) assert_dom_equal expected, actual end def test_form_tag_with_remote actual = form_tag({}, :remote => true) - expected = %() + + expected = whole_form("http://www.example.com", :remote => true) assert_dom_equal expected, actual end def test_form_tag_with_remote_false actual = form_tag({}, :remote => false) - expected = %() + + expected = whole_form assert_dom_equal expected, actual end def test_form_tag_with_block_in_erb - output_buffer = form_tag("http://example.com") { concat "Hello world!" } + output_buffer = form_tag("http://www.example.com") { concat "Hello world!" } - expected = %(Hello world!
) + expected = whole_form { "Hello world!" } assert_dom_equal expected, output_buffer end def test_form_tag_with_block_and_method_in_erb - output_buffer = form_tag("http://example.com", :method => :put) { concat "Hello world!" } + output_buffer = form_tag("http://www.example.com", :method => :put) { concat "Hello world!" } + + expected = whole_form("http://www.example.com", :method => "put") do + "Hello world!" + end - expected = %(
Hello world!
) assert_dom_equal expected, output_buffer end diff --git a/actionpack/test/template/html-scanner/sanitizer_test.rb b/actionpack/test/template/html-scanner/sanitizer_test.rb index a6e760b0b68..c9edde88926 100644 --- a/actionpack/test/template/html-scanner/sanitizer_test.rb +++ b/actionpack/test/template/html-scanner/sanitizer_test.rb @@ -257,6 +257,10 @@ class SanitizerTest < ActionController::TestCase assert_sanitized %{my link} end + def test_should_sanitize_neverending_attribute + assert_sanitized "" + end + protected def assert_sanitized(input, expected = nil) @sanitizer ||= HTML::WhiteListSanitizer.new diff --git a/actionpack/test/template/tag_helper_test.rb b/actionpack/test/template/tag_helper_test.rb index ec5fe3d1d76..507cdca8d0a 100644 --- a/actionpack/test/template/tag_helper_test.rb +++ b/actionpack/test/template/tag_helper_test.rb @@ -95,9 +95,9 @@ class TagHelperTest < ActionView::TestCase assert_equal '1 < 2 & 3', escape_once('1 < 2 & 3') end - def test_double_escaping_attributes + def test_tag_honors_html_safe_for_param_values ['1&2', '1 < 2', '“test“'].each do |escaped| - assert_equal %(), tag('a', :href => escaped) + assert_equal %(), tag('a', :href => escaped.html_safe) end end diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index 72d48976303..765beebfa3f 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -40,19 +40,8 @@ class UrlHelperTest < ActiveSupport::TestCase end alias url_hash hash_for - def test_url_for_escapes_urls + def test_url_for_does_not_escape_urls assert_equal "/?a=b&c=d", url_for(abcd) - assert_equal "/?a=b&c=d", url_for(abcd(:escape => true)) - assert_equal "/?a=b&c=d", url_for(abcd(:escape => false)) - end - - def test_url_for_escaping_is_safety_aware - assert url_for(abcd(:escape => true)).html_safe?, "escaped urls should be html_safe?" - assert !url_for(abcd(:escape => false)).html_safe?, "non-escaped urls should not be html_safe?" - end - - def test_url_for_escapes_url_once - assert_equal "/?a=b&c=d", url_for("/?a=b&c=d") end def test_url_for_with_back @@ -67,11 +56,6 @@ class UrlHelperTest < ActiveSupport::TestCase assert_equal 'javascript:history.back()', url_for(:back) end - def test_url_for_from_hash_doesnt_escape_ampersand - path = url_for(hash_for(:foo => :bar, :baz => :quux)) - assert_equal '/?baz=quux&foo=bar', sort_query_string_params(path) - end - # todo: missing test cases def test_button_to_with_straight_url assert_dom_equal "
", button_to("Hello", "http://www.example.com") @@ -81,8 +65,8 @@ class UrlHelperTest < ActiveSupport::TestCase assert_dom_equal "
", button_to("Hello", "http://www.example.com/q1=v1&q2=v2") end - def test_button_to_with_escaped_query - assert_dom_equal "
", button_to("Hello", "http://www.example.com/q1=v1&q2=v2") + def test_button_to_with_html_safe_URL + assert_dom_equal "
", button_to("Hello", "http://www.example.com/q1=v1&q2=v2".html_safe) end def test_button_to_with_query_and_no_name @@ -151,13 +135,12 @@ class UrlHelperTest < ActiveSupport::TestCase def test_link_tag_with_query expected = %{
Hello} - assert_dom_equal expected, link_to("Hello", "http://www.example.com?q1=v1&q2=v2") + assert_dom_equal expected, link_to("Hello", "http://www.example.com?q1=v1&q2=v2") end def test_link_tag_with_query_and_no_name - link = link_to(nil, "http://www.example.com?q1=v1&q2=v2") expected = %{http://www.example.com?q1=v1&q2=v2} - assert_dom_equal expected, link + assert_dom_equal expected, link_to(nil, "http://www.example.com?q1=v1&q2=v2") end def test_link_tag_with_back @@ -312,7 +295,7 @@ class UrlHelperTest < ActiveSupport::TestCase @request = request_for_url("/?order=desc&page=1") assert current_page?(hash_for(:order => "desc", :page => "1")) - assert current_page?("http://www.example.com/?order=desc&page=1") + assert current_page?("http://www.example.com/?order=desc&page=1") end def test_link_unless_current @@ -345,7 +328,7 @@ class UrlHelperTest < ActiveSupport::TestCase link_to_unless_current("Showing", "http://www.example.com/?order=asc") @request = request_for_url("/?order=desc") - assert_equal %{Showing}, + assert_equal %{Showing}, link_to_unless_current("Showing", hash_for(:order => "desc", :page => 2)) assert_equal %{Showing}, link_to_unless_current("Showing", "http://www.example.com/?order=desc&page=2") @@ -415,7 +398,7 @@ class UrlHelperTest < ActiveSupport::TestCase private def sort_query_string_params(uri) path, qs = uri.split('?') - qs = qs.split('&').sort.join('&') if qs + qs = qs.split('&').sort.join('&') if qs qs ? "#{path}?#{qs}" : path end end diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb index 8e77a0222ee..e1d7d40c474 100644 --- a/activemodel/test/cases/validations/numericality_validation_test.rb +++ b/activemodel/test/cases/validations/numericality_validation_test.rb @@ -4,6 +4,8 @@ require 'cases/helper' require 'models/topic' require 'models/person' +require 'bigdecimal' + class NumericalityValidationTest < ActiveModel::TestCase def teardown diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 09f897eaac2..a1a82fdff59 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,6 +1,12 @@ *Rails 3.0.0 [RC1] (unreleased)* -* PostgreSQL: ensure the database time zone matches Ruby's time zone. #4895 [Aaron Patterson] +* Add scoping and unscoped as the syntax to replace the old with_scope and with_exclusive_scope [José Valim] + +* New rake task, db:migrate:status, displays status of migrations #4947 [Kevin Skoglund] + +* select and order for ActiveRecord now always concatenate nested calls. Use reorder if you want the original order to be overwritten [Santiago Pastorino] + +* PostgreSQL: ensure the database time zone matches Ruby's time zone #4895 [Aaron Patterson] *Rails 3.0.0 [beta 4] (June 8th, 2010)* diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 22a17a62af4..392b717e0a2 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -62,7 +62,9 @@ end adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z]+/] puts [adapter, adapter_short, connection_path].inspect ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')) - Dir["test/cases/**/*_test{,_#{adapter_short}}.rb"].all? do |file| + (Dir["test/cases/**/*_test.rb"].reject { + |x| x =~ /\/adapters\// + } + Dir["test/cases/adapters/#{adapter_short}/**/*_test.rb"]).all? do |file| system(ruby, "-Ilib:test:#{connection_path}", file) end or raise "Failures" end diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 186b531ffb1..ddf4ce4058e 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -409,6 +409,17 @@ module ActiveRecord end def method_missing(method, *args) + case method.to_s + when 'find_or_create' + return find(:first, :conditions => args.first) || create(args.first) + when /^find_or_create_by_(.*)$/ + rest = $1 + return send("find_by_#{rest}", *args) || + method_missing("create_by_#{rest}", *args) + when /^create_by_(.*)$/ + return create Hash[$1.split('_and_').zip(args)] + end + if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method)) if block_given? super { |*block_args| yield(*block_args) } diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index 92c6b3e7700..d74fb7c702b 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -110,7 +110,11 @@ module ActiveRecord create_scoping = {} set_belongs_to_association_for(create_scoping) { - :find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit], :include => @reflection.options[:include]}, + :find => { :conditions => @finder_sql, + :readonly => false, + :order => @reflection.options[:order], + :limit => @reflection.options[:limit], + :include => @reflection.options[:include]}, :create => create_scoping } end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index e7b52287a59..c8795e44960 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -15,6 +15,7 @@ require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/string/behavior' require 'active_support/core_ext/kernel/singleton_class' require 'active_support/core_ext/module/delegation' +require 'active_support/core_ext/module/deprecation' require 'active_support/core_ext/module/introspection' require 'active_support/core_ext/object/duplicable' require 'active_support/core_ext/object/blank' @@ -278,6 +279,18 @@ module ActiveRecord #:nodoc: # on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+. cattr_accessor :logger, :instance_writer => false + class << self + def reset_subclasses #:nodoc: + ActiveSupport::Deprecation.warn 'ActiveRecord::Base.reset_subclasses no longer does anything in Rails 3. It will be removed in the final release; please update your apps and plugins.', caller + end + + def subclasses + descendants + end + + deprecate :subclasses => :descendants + end + ## # :singleton-method: # Contains the database configuration - as is typically stored in config/database.yml - @@ -385,7 +398,7 @@ module ActiveRecord #:nodoc: delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped delegate :find_each, :find_in_batches, :to => :scoped - delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped + delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped # Executes a custom SQL query against your database and returns all the results. The results will @@ -707,14 +720,6 @@ module ActiveRecord #:nodoc: end alias :sequence_name= :set_sequence_name - # Turns the +table_name+ back into a class name following the reverse rules of +table_name+. - def class_name(table_name = table_name) # :nodoc: - # remove any prefix and/or suffix from the table name - class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].camelize - class_name = class_name.singularize if pluralize_table_names - class_name - end - # Indicates whether the table associated with this class exists def table_exists? connection.table_exists?(table_name) @@ -788,7 +793,7 @@ module ActiveRecord #:nodoc: def reset_column_information undefine_attribute_methods @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil - @arel_engine = @unscoped = @arel_table = nil + @arel_engine = @relation = @arel_table = nil end def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc: @@ -891,9 +896,9 @@ module ActiveRecord #:nodoc: store_full_sti_class ? name : name.demodulize end - def unscoped - @unscoped ||= Relation.new(self, arel_table) - finder_needs_type_condition? ? @unscoped.where(type_condition) : @unscoped + def relation + @relation ||= Relation.new(self, arel_table) + finder_needs_type_condition? ? @relation.where(type_condition) : @relation end def arel_table @@ -910,6 +915,31 @@ module ActiveRecord #:nodoc: end end + # Returns a scope for this class without taking into account the default_scope. + # + # class Post < ActiveRecord::Base + # default_scope :published => true + # end + # + # Post.all # Fires "SELECT * FROM posts WHERE published = true" + # Post.unscoped.all # Fires "SELECT * FROM posts" + # + # This method also accepts a block meaning that all queries inside the block will + # not use the default_scope: + # + # Post.unscoped { + # limit(10) # Fires "SELECT * FROM posts LIMIT 10" + # } + # + def unscoped + block_given? ? relation.scoping { yield } : relation + end + + def scoped_methods #:nodoc: + key = :"#{self}_scoped_methods" + Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup + end + private # Finder methods must instantiate through this method to work with the # single-table inheritance model that makes it possible to create @@ -1144,6 +1174,20 @@ module ActiveRecord #:nodoc: # Works like with_scope, but discards any nested properties. def with_exclusive_scope(method_scoping = {}, &block) + if method_scoping.values.any? { |e| e.is_a?(ActiveRecord::Relation) } + raise ArgumentError, <<-MSG +New finder API can not be used with_exclusive_scope. You can either call unscoped to get an anonymous scope not bound to the default_scope: + + User.unscoped.where(:active => true) + +Or call unscoped with a block: + + User.unscoped do + User.where(:active => true).all + end + +MSG + end with_scope(method_scoping, :overwrite, &block) end @@ -1157,11 +1201,6 @@ module ActiveRecord #:nodoc: self.default_scoping << construct_finder_arel(options, default_scoping.pop) end - def scoped_methods #:nodoc: - key = :"#{self}_scoped_methods" - Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup - end - def current_scoped_methods #:nodoc: scoped_methods.last end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 0216a8f4acd..76b65bf2198 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -365,7 +365,7 @@ module ActiveRecord end def remove_index!(table_name, index_name) #:nodoc: - execute "DROP INDEX #{quote_column_name(index_name)} ON #{table_name}" + execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}" end # Rename an index. diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index b273c33e506..4c5e1ae2188 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/kernel/singleton_class' +require 'active_support/core_ext/module/aliasing' module ActiveRecord # Exception that can be raised to stop migrations from going backwards. diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index ec0a98c6df3..c010dac64ed 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -25,10 +25,9 @@ module ActiveRecord # # You can define a \scope that applies to all finders using # ActiveRecord::Base.default_scope. - def scoped(options = {}, &block) + def scoped(options = nil) if options.present? - relation = scoped.apply_finder_options(options) - block_given? ? relation.extending(Module.new(&block)) : relation + scoped.apply_finder_options(options) else current_scoped_methods ? unscoped.merge(current_scoped_methods) : unscoped.clone end @@ -88,18 +87,22 @@ module ActiveRecord # end def scope(name, scope_options = {}, &block) name = name.to_sym + valid_scope_name?(name) - if !scopes[name] && respond_to?(name, true) - logger.warn "Creating scope :#{name}. " \ - "Overwriting existing method #{self.name}.#{name}." - end + extension = Module.new(&block) if block_given? scopes[name] = lambda do |*args| options = scope_options.is_a?(Proc) ? scope_options.call(*args) : scope_options - relation = scoped - relation = options.is_a?(Hash) ? relation.apply_finder_options(options) : scoped.merge(options) if options - block_given? ? relation.extending(Module.new(&block)) : relation + relation = if options.is_a?(Hash) + scoped.apply_finder_options(options) + elsif options + scoped.merge(options) + else + scoped + end + + extension ? relation.extending(extension) : relation end singleton_class.send :define_method, name, &scopes[name] @@ -109,7 +112,15 @@ module ActiveRecord ActiveSupport::Deprecation.warn("Base.named_scope has been deprecated, please use Base.scope instead", caller) scope(*args, &block) end - end + protected + + def valid_scope_name?(name) + if !scopes[name] && respond_to?(name, true) + logger.warn "Creating scope :#{name}. " \ + "Overwriting existing method #{self.name}.#{name}." + end + end + end end end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 9e28aa2a058..50166c43857 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -182,7 +182,7 @@ module ActiveRecord def reload(options = nil) clear_aggregation_cache clear_association_cache - @attributes.update(self.class.send(:with_exclusive_scope) { self.class.find(self.id, options) }.instance_variable_get('@attributes')) + @attributes.update(self.class.unscoped { self.class.find(self.id, options) }.instance_variable_get('@attributes')) @attributes_cache = {} self end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 006e64b115f..7882f05d07a 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -57,7 +57,7 @@ namespace :db do end rescue case config['adapter'] - when 'mysql' + when /mysql/ @charset = ENV['CHARSET'] || 'utf8' @collation = ENV['COLLATION'] || 'utf8_unicode_ci' creation_options = {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)} @@ -171,6 +171,36 @@ namespace :db do ActiveRecord::Migrator.run(:down, "db/migrate/", version) Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby end + + desc "Display status of migrations" + task :status => :environment do + config = ActiveRecord::Base.configurations[Rails.env || 'development'] + ActiveRecord::Base.establish_connection(config) + unless ActiveRecord::Base.connection.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name) + puts 'Schema migrations table does not exist yet.' + next # means "return" for rake task + end + db_list = ActiveRecord::Base.connection.select_values("SELECT version FROM #{ActiveRecord::Migrator.schema_migrations_table_name}") + file_list = [] + Dir.foreach(File.join(Rails.root, 'db', 'migrate')) do |file| + # only files matching "20091231235959_some_name.rb" pattern + if match_data = /(\d{14})_(.+)\.rb/.match(file) + status = db_list.delete(match_data[1]) ? 'up' : 'down' + file_list << [status, match_data[1], match_data[2]] + end + end + # output + puts "\ndatabase: #{config['database']}\n\n" + puts "#{"Status".center(8)} #{"Migration ID".ljust(14)} Migration Name" + puts "-" * 50 + file_list.each do |file| + puts "#{file[0].center(8)} #{file[1].ljust(14)} #{file[2].humanize}" + end + db_list.each do |version| + puts "#{'up'.center(8)} #{version.ljust(14)} *** NO FILE ***" + end + puts + end end desc 'Rolls the schema back to the previous version (specify steps w/ STEP=n).' @@ -194,7 +224,7 @@ namespace :db do task :charset => :environment do config = ActiveRecord::Base.configurations[Rails.env || 'development'] case config['adapter'] - when 'mysql' + when /mysql/ ActiveRecord::Base.establish_connection(config) puts ActiveRecord::Base.connection.charset when 'postgresql' @@ -212,7 +242,7 @@ namespace :db do task :collation => :environment do config = ActiveRecord::Base.configurations[Rails.env || 'development'] case config['adapter'] - when 'mysql' + when /mysql/ ActiveRecord::Base.establish_connection(config) puts ActiveRecord::Base.connection.collation else @@ -313,7 +343,7 @@ namespace :db do task :dump => :environment do abcs = ActiveRecord::Base.configurations case abcs[Rails.env]["adapter"] - when "mysql", "oci", "oracle" + when /mysql/, "oci", "oracle" ActiveRecord::Base.establish_connection(abcs[Rails.env]) File.open("#{Rails.root}/db/#{Rails.env}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump } when "postgresql" @@ -361,7 +391,7 @@ namespace :db do task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do abcs = ActiveRecord::Base.configurations case abcs["test"]["adapter"] - when "mysql" + when /mysql/ ActiveRecord::Base.establish_connection(:test) ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0') IO.readlines("#{Rails.root}/db/#{Rails.env}_structure.sql").join.split("\n\n").each do |table| @@ -395,7 +425,7 @@ namespace :db do task :purge => :environment do abcs = ActiveRecord::Base.configurations case abcs["test"]["adapter"] - when "mysql" + when /mysql/ ActiveRecord::Base.establish_connection(:test) ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"], abcs["test"]) when "postgresql" @@ -451,7 +481,7 @@ task 'test:prepare' => 'db:test:prepare' def drop_database(config) case config['adapter'] - when 'mysql' + when /mysql/ ActiveRecord::Base.establish_connection(config) ActiveRecord::Base.connection.drop_database config['database'] when /^sqlite/ diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index fd0660a138b..bc708b573f7 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -16,7 +16,7 @@ module ActiveRecord attr_reader :table, :klass attr_accessor :extensions - def initialize(klass, table, &block) + def initialize(klass, table) @klass, @table = klass, table @implicit_readonly = nil @@ -25,12 +25,10 @@ module ActiveRecord SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)} (ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])} @extensions = [] - - apply_modules(Module.new(&block)) if block_given? end def new(*args, &block) - with_create_scope { @klass.new(*args, &block) } + scoping { @klass.new(*args, &block) } end def initialize_copy(other) @@ -40,11 +38,11 @@ module ActiveRecord alias build new def create(*args, &block) - with_create_scope { @klass.create(*args, &block) } + scoping { @klass.create(*args, &block) } end def create!(*args, &block) - with_create_scope { @klass.create!(*args, &block) } + scoping { @klass.create!(*args, &block) } end def respond_to?(method, include_private = false) @@ -102,6 +100,25 @@ module ActiveRecord end end + # Scope all queries to the current scope. + # + # ==== Example + # + # Comment.where(:post_id => 1).scoping do + # Comment.first #=> SELECT * FROM comments WHERE post_id = 1 + # end + # + # Please check unscoped if you want to remove all previous scopes (including + # the default_scope) during the execution of a block. + def scoping + @klass.scoped_methods << self + begin + yield + ensure + @klass.scoped_methods.pop + end + end + # Updates all records with details given if they match a set of conditions supplied, limits and order can # also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the # database. It does not instantiate the involved models and it does not trigger Active Record callbacks @@ -305,7 +322,6 @@ module ActiveRecord if where.is_a?(Arel::Predicates::Equality) hash[where.operand1.name] = where.operand2.respond_to?(:value) ? where.operand2.value : where.operand2 end - hash end end @@ -328,15 +344,6 @@ module ActiveRecord to_a.inspect end - def extend(*args, &block) - if block_given? - apply_modules Module.new(&block) - self - else - super - end - end - protected def method_missing(method, *args, &block) @@ -345,7 +352,7 @@ module ActiveRecord elsif @klass.scopes[method] merge(@klass.send(method, *args, &block)) elsif @klass.respond_to?(method) - @klass.send(:with_scope, self) { @klass.send(method, *args, &block) } + scoping { @klass.send(method, *args, &block) } elsif arel.respond_to?(method) arel.send(method, *args, &block) elsif match = DynamicFinderMatch.match(method) @@ -364,10 +371,6 @@ module ActiveRecord private - def with_create_scope - @klass.send(:with_scope, :create => scope_for_create, :find => {}) { yield } - end - def references_eager_loaded_tables? # always convert table names to downcase as in Oracle quoted table names are in uppercase joined_tables = (tables_in_string(arel.joins(arel)) + [table.name, table.table_alias]).compact.map(&:downcase).uniq diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index d853fd63d1b..5cea2328e89 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -20,9 +20,7 @@ module ActiveRecord table = Arel::Table.new(table_name, :engine => @engine) end - unless attribute = table[column] - raise StatementInvalid, "No attribute named `#{column}` exists for table `#{table.name}`" - end + attribute = table[column] || Arel::Attribute.new(table, column) case value when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::Relation diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 015ca8c24cc..46922712668 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -15,12 +15,10 @@ module ActiveRecord end def eager_load(*args) - args.reject! { |a| a.blank? } clone.tap { |r| r.eager_load_values += args if args.present? } end def preload(*args) - args.reject! { |a| a.blank? } clone.tap { |r| r.preload_values += args if args.present? } end @@ -28,29 +26,24 @@ module ActiveRecord if block_given? to_a.select { |*block_args| yield(*block_args) } else - args.reject! { |a| a.blank? } clone.tap { |r| r.select_values += args if args.present? } end end def group(*args) - args.reject! { |a| a.blank? } clone.tap { |r| r.group_values += args if args.present? } end def order(*args) - args.reject! { |a| a.blank? } clone.tap { |r| r.order_values += args if args.present? } end def reorder(*args) - args.reject! { |a| a.blank? } clone.tap { |r| r.order_values = args if args.present? } end def joins(*args) args.flatten! - args.reject! { |a| a.blank? } clone.tap { |r| r.joins_values += args if args.present? } end @@ -93,8 +86,9 @@ module ActiveRecord clone.tap { |r| r.from_value = value } end - def extending(*modules) - clone.tap { |r| r.send :apply_modules, *modules } + def extending(*modules, &block) + modules << Module.new(&block) if block_given? + clone.tap { |r| r.send(:apply_modules, *modules) } end def reverse_order @@ -201,7 +195,7 @@ module ActiveRecord stashed_association_joins = joins.select {|j| j.is_a?(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)} - non_association_joins = (joins - association_joins - stashed_association_joins).reject {|j| j.blank?} + non_association_joins = (joins - association_joins - stashed_association_joins) custom_joins = custom_join_sql(*non_association_joins) join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, custom_joins) diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index bb1f138f5b3..7712ad2569b 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -6,7 +6,7 @@ module ActiveRecord merged_relation = clone return merged_relation unless r - (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).reject {|m| [:joins, :where].include?(m)}.each do |method| + ((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) - [:joins, :where]).each do |method| value = r.send(:"#{method}_values") merged_relation.send(:"#{method}_values=", value) if value.present? end diff --git a/activerecord/test/cases/adapters/sqlite/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb similarity index 100% rename from activerecord/test/cases/adapters/sqlite/copy_table_test.rb rename to activerecord/test/cases/adapters/sqlite3/copy_table_test.rb diff --git a/activerecord/test/cases/adapters/sqlite/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb similarity index 100% rename from activerecord/test/cases/adapters/sqlite/sqlite3_adapter_test.rb rename to activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 45c74980135..5e3ba778f33 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -21,6 +21,68 @@ class HasManyAssociationsTest < ActiveRecord::TestCase Client.destroyed_client_ids.clear end + def test_create_by + person = Person.create! :first_name => 'tenderlove' + post = Post.find :first + + assert_equal [], person.readers + assert_nil person.readers.find_by_post_id post.id + + reader = person.readers.create_by_post_id post.id + + assert_equal 1, person.readers.count + assert_equal 1, person.readers.length + assert_equal post, person.readers.first.post + assert_equal person, person.readers.first.person + end + + def test_create_by_multi + person = Person.create! :first_name => 'tenderlove' + post = Post.find :first + + assert_equal [], person.readers + + reader = person.readers.create_by_post_id_and_skimmer post.id, false + + assert_equal 1, person.readers.count + assert_equal 1, person.readers.length + assert_equal post, person.readers.first.post + assert_equal person, person.readers.first.person + end + + def test_find_or_create_by + person = Person.create! :first_name => 'tenderlove' + post = Post.find :first + + assert_equal [], person.readers + assert_nil person.readers.find_by_post_id post.id + + reader = person.readers.find_or_create_by_post_id post.id + + assert_equal 1, person.readers.count + assert_equal 1, person.readers.length + assert_equal post, person.readers.first.post + assert_equal person, person.readers.first.person + end + + def test_find_or_create + person = Person.create! :first_name => 'tenderlove' + post = Post.find :first + + assert_equal [], person.readers + assert_nil person.readers.find(:first, :conditions => { + :post_id => post.id + }) + + reader = person.readers.find_or_create :post_id => post.id + + assert_equal 1, person.readers.count + assert_equal 1, person.readers.length + assert_equal post, person.readers.first.post + assert_equal person, person.readers.first.person + end + + def force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.each {|f| } end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index f8b90d88778..ba7db838ca2 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -798,25 +798,6 @@ class BasicsTest < ActiveRecord::TestCase assert_raise(NoMethodError) { t.title2 } end - def test_class_name - assert_equal "Firm", ActiveRecord::Base.class_name("firms") - assert_equal "Category", ActiveRecord::Base.class_name("categories") - assert_equal "AccountHolder", ActiveRecord::Base.class_name("account_holder") - - ActiveRecord::Base.pluralize_table_names = false - assert_equal "Firms", ActiveRecord::Base.class_name( "firms" ) - ActiveRecord::Base.pluralize_table_names = true - - ActiveRecord::Base.table_name_prefix = "test_" - assert_equal "Firm", ActiveRecord::Base.class_name( "test_firms" ) - ActiveRecord::Base.table_name_suffix = "_tests" - assert_equal "Firm", ActiveRecord::Base.class_name( "test_firms_tests" ) - ActiveRecord::Base.table_name_prefix = "" - assert_equal "Firm", ActiveRecord::Base.class_name( "firms_tests" ) - ActiveRecord::Base.table_name_suffix = "" - assert_equal "Firm", ActiveRecord::Base.class_name( "firms" ) - end - def test_null_fields assert_nil Topic.find(1).parent_id assert_nil Topic.create("title" => "Hey you").parent_id diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index c1c8f01e46a..8c09fc4d59d 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -173,7 +173,7 @@ class InheritanceTest < ActiveRecord::TestCase def test_complex_inheritance very_special_client = VerySpecialClient.create("name" => "veryspecial") - assert_equal very_special_client, VerySpecialClient.find(:first, :conditions => "name = 'veryspecial'") + assert_equal very_special_client, VerySpecialClient.where("name = 'veryspecial'").first assert_equal very_special_client, SpecialClient.find(:first, :conditions => "name = 'veryspecial'") assert_equal very_special_client, Company.find(:first, :conditions => "name = 'veryspecial'") assert_equal very_special_client, Client.find(:first, :conditions => "name = 'veryspecial'") diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index 6cd42ff9361..4e8ce1dac10 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -1,3 +1,7 @@ +# This file can be removed once with_exclusive_scope and with_scope are removed. +# All the tests were already ported to relation_scoping_test.rb when the new +# relation scoping API was added. + require "cases/helper" require 'models/post' require 'models/author' @@ -283,6 +287,12 @@ class NestedScopingTest < ActiveRecord::TestCase end end + def test_with_exclusive_scope_with_relation + assert_raise(ArgumentError) do + Developer.all_johns + end + end + def test_append_conditions Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do Developer.send(:with_scope, :find => { :conditions => 'salary = 80000' }) do @@ -533,205 +543,4 @@ class NestedScopingTest < ActiveRecord::TestCase assert_equal 1, scoped_authors.size assert_equal authors(:david).attributes, scoped_authors.first.attributes end -end - -class HasManyScopingTest< ActiveRecord::TestCase - fixtures :comments, :posts - - def setup - @welcome = Post.find(1) - end - - def test_forwarding_of_static_methods - assert_equal 'a comment...', Comment.what_are_you - assert_equal 'a comment...', @welcome.comments.what_are_you - end - - def test_forwarding_to_scoped - assert_equal 4, Comment.search_by_type('Comment').size - assert_equal 2, @welcome.comments.search_by_type('Comment').size - end - - def test_forwarding_to_dynamic_finders - assert_equal 4, Comment.find_all_by_type('Comment').size - assert_equal 2, @welcome.comments.find_all_by_type('Comment').size - end - - def test_nested_scope - Comment.send(:with_scope, :find => { :conditions => '1=1' }) do - assert_equal 'a comment...', @welcome.comments.what_are_you - end - end -end - -class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase - fixtures :posts, :categories, :categories_posts - - def setup - @welcome = Post.find(1) - end - - def test_forwarding_of_static_methods - assert_equal 'a category...', Category.what_are_you - assert_equal 'a category...', @welcome.categories.what_are_you - end - - def test_forwarding_to_dynamic_finders - assert_equal 4, Category.find_all_by_type('SpecialCategory').size - assert_equal 0, @welcome.categories.find_all_by_type('SpecialCategory').size - assert_equal 2, @welcome.categories.find_all_by_type('Category').size - end - - def test_nested_scope - Category.send(:with_scope, :find => { :conditions => '1=1' }) do - assert_equal 'a comment...', @welcome.comments.what_are_you - end - end -end - -class DefaultScopingTest < ActiveRecord::TestCase - fixtures :developers, :posts - - def test_default_scope - expected = Developer.find(:all, :order => 'salary DESC').collect { |dev| dev.salary } - received = DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary } - assert_equal expected, received - end - - def test_default_scope_with_conditions_string - assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.find(:all).map(&:id).sort - assert_equal nil, DeveloperCalledDavid.create!.name - end - - def test_default_scope_with_conditions_hash - assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.find(:all).map(&:id).sort - assert_equal 'Jamis', DeveloperCalledJamis.create!.name - end - - def test_default_scoping_with_threads - 2.times do - Thread.new { assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values }.join - end - end - - def test_default_scoping_with_inheritance - # Inherit a class having a default scope and define a new default scope - klass = Class.new(DeveloperOrderedBySalary) - klass.send :default_scope, :limit => 1 - - # Scopes added on children should append to parent scope - assert_equal 1, klass.scoped.limit_value - assert_equal ['salary DESC'], klass.scoped.order_values - - # Parent should still have the original scope - assert_nil DeveloperOrderedBySalary.scoped.limit_value - assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values - end - - def test_default_scope_called_twice_merges_conditions - Developer.destroy_all - Developer.create!(:name => "David", :salary => 80000) - Developer.create!(:name => "David", :salary => 100000) - Developer.create!(:name => "Brian", :salary => 100000) - - klass = Class.new(Developer) - klass.__send__ :default_scope, :conditions => { :name => "David" } - klass.__send__ :default_scope, :conditions => { :salary => 100000 } - assert_equal 1, klass.count - assert_equal "David", klass.first.name - assert_equal 100000, klass.first.salary - end - def test_method_scope - expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary } - received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary } - assert_equal expected, received - end - - def test_nested_scope - expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary } - received = DeveloperOrderedBySalary.send(:with_scope, :find => { :order => 'name DESC'}) do - DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary } - end - assert_equal expected, received - end - - def test_named_scope_overwrites_default - expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.name } - received = DeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.name } - assert_equal expected, received - end - - def test_nested_exclusive_scope - expected = Developer.find(:all, :limit => 100).collect { |dev| dev.salary } - received = DeveloperOrderedBySalary.send(:with_exclusive_scope, :find => { :limit => 100 }) do - DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary } - end - assert_equal expected, received - end - - def test_overwriting_default_scope - expected = Developer.find(:all, :order => 'salary').collect { |dev| dev.salary } - received = DeveloperOrderedBySalary.find(:all, :order => 'salary').collect { |dev| dev.salary } - assert_equal expected, received - end - - def test_default_scope_using_relation - posts = PostWithComment.scoped - assert_equal 2, posts.count - assert_equal posts(:thinking), posts.first - end - - def test_create_attribute_overwrites_default_scoping - assert_equal 'David', PoorDeveloperCalledJamis.create!(:name => 'David').name - assert_equal 200000, PoorDeveloperCalledJamis.create!(:name => 'David', :salary => 200000).salary - end - - def test_create_attribute_overwrites_default_values - assert_equal nil, PoorDeveloperCalledJamis.create!(:salary => nil).salary - assert_equal 50000, PoorDeveloperCalledJamis.create!(:name => 'David').salary - end -end - -=begin -# We disabled the scoping for has_one and belongs_to as we can't think of a proper use case - -class BelongsToScopingTest< ActiveRecord::TestCase - fixtures :comments, :posts - - def setup - @greetings = Comment.find(1) - end - - def test_forwarding_of_static_method - assert_equal 'a post...', Post.what_are_you - assert_equal 'a post...', @greetings.post.what_are_you - end - - def test_forwarding_to_dynamic_finders - assert_equal 4, Post.find_all_by_type('Post').size - assert_equal 1, @greetings.post.find_all_by_type('Post').size - end - -end - -class HasOneScopingTest< ActiveRecord::TestCase - fixtures :comments, :posts - - def setup - @sti_comments = Post.find(4) - end - - def test_forwarding_of_static_methods - assert_equal 'a comment...', Comment.what_are_you - assert_equal 'a very special comment...', @sti_comments.very_special_comment.what_are_you - end - - def test_forwarding_to_dynamic_finders - assert_equal 1, Comment.find_all_by_type('VerySpecialComment').size - assert_equal 1, @sti_comments.very_special_comment.find_all_by_type('VerySpecialComment').size - assert_equal 0, @sti_comments.very_special_comment.find_all_by_type('Comment').size - end - -end - -=end +end \ No newline at end of file diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 99a3a12a8bf..4ce9bdb46d5 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -1621,6 +1621,23 @@ if ActiveRecord::Base.connection.supports_migrations? end end + class ReservedWordsMigrationTest < ActiveRecord::TestCase + def test_drop_index_from_table_named_values + connection = Person.connection + connection.create_table :values, :force => true do |t| + t.integer :value + end + + assert_nothing_raised do + connection.add_index :values, :value + connection.remove_index :values, :column => :value + end + + connection.drop_table :values rescue nil + end + end + + class ChangeTableMigrationsTest < ActiveRecord::TestCase def setup @connection = Person.connection diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index 33ffb041c18..dc85b395d3b 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -54,8 +54,8 @@ class NamedScopeTest < ActiveRecord::TestCase end def test_respond_to_respects_include_private_parameter - assert !Topic.approved.respond_to?(:with_create_scope) - assert Topic.approved.respond_to?(:with_create_scope, true) + assert !Topic.approved.respond_to?(:tables_in_string) + assert Topic.approved.respond_to?(:tables_in_string, true) end def test_subclasses_inherit_scopes @@ -450,6 +450,12 @@ class NamedScopeTest < ActiveRecord::TestCase assert before.object_id != post.comments.containing_the_letter_e.object_id, "AssociationCollection##{method} should reset the named scopes cache" end end + + def test_named_scoped_are_lazy_loaded_if_table_still_does_not_exist + assert_nothing_raised do + require "models/without_table" + end + end end class DynamicScopeMatchTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb new file mode 100644 index 00000000000..41dcdbcd371 --- /dev/null +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -0,0 +1,396 @@ +require "cases/helper" +require 'models/post' +require 'models/author' +require 'models/developer' +require 'models/project' +require 'models/comment' +require 'models/category' + +class RelationScopingTest < ActiveRecord::TestCase + fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects + + def test_scoped_find + Developer.where("name = 'David'").scoping do + assert_nothing_raised { Developer.find(1) } + end + end + + def test_scoped_find_first + developer = Developer.find(10) + Developer.where("salary = 100000").scoping do + assert_equal developer, Developer.order("name").first + end + end + + def test_scoped_find_last + highest_salary = Developer.order("salary DESC").first + + Developer.order("salary").scoping do + assert_equal highest_salary, Developer.last + end + end + + def test_scoped_find_last_preserves_scope + lowest_salary = Developer.first :order => "salary ASC" + highest_salary = Developer.first :order => "salary DESC" + + Developer.order("salary").scoping do + assert_equal highest_salary, Developer.last + assert_equal lowest_salary, Developer.first + end + end + + def test_scoped_find_combines_and_sanitizes_conditions + Developer.where("salary = 9000").scoping do + assert_equal developers(:poor_jamis), Developer.where("name = 'Jamis'").first + end + end + + def test_scoped_find_all + Developer.where("name = 'David'").scoping do + assert_equal [developers(:david)], Developer.all + end + end + + def test_scoped_find_select + Developer.select("id, name").scoping do + developer = Developer.where("name = 'David'").first + assert_equal "David", developer.name + assert !developer.has_attribute?(:salary) + end + end + + def test_scope_select_concatenates + Developer.select("id, name").scoping do + developer = Developer.select('id, salary').where("name = 'David'").first + assert_equal 80000, developer.salary + assert developer.has_attribute?(:id) + assert developer.has_attribute?(:name) + assert developer.has_attribute?(:salary) + end + end + + def test_scoped_count + Developer.where("name = 'David'").scoping do + assert_equal 1, Developer.count + end + + Developer.where('salary = 100000').scoping do + assert_equal 8, Developer.count + assert_equal 1, Developer.where("name LIKE 'fixture_1%'").count + end + end + + def test_scoped_find_include + # with the include, will retrieve only developers for the given project + scoped_developers = Developer.includes(:projects).scoping do + Developer.where('projects.id = 2').all + end + assert scoped_developers.include?(developers(:david)) + assert !scoped_developers.include?(developers(:jamis)) + assert_equal 1, scoped_developers.size + end + + def test_scoped_find_joins + scoped_developers = Developer.joins('JOIN developers_projects ON id = developer_id').scoping do + Developer.where('developers_projects.project_id = 2').all + end + + assert scoped_developers.include?(developers(:david)) + assert !scoped_developers.include?(developers(:jamis)) + assert_equal 1, scoped_developers.size + assert_equal developers(:david).attributes, scoped_developers.first.attributes + end + + def test_scoped_create_with_where + new_comment = VerySpecialComment.where(:post_id => 1).scoping do + VerySpecialComment.create :body => "Wonderful world" + end + + assert_equal 1, new_comment.post_id + assert Post.find(1).comments.include?(new_comment) + end + + def test_scoped_create_with_create_with + new_comment = VerySpecialComment.create_with(:post_id => 1).scoping do + VerySpecialComment.create :body => "Wonderful world" + end + + assert_equal 1, new_comment.post_id + assert Post.find(1).comments.include?(new_comment) + end + + def test_scoped_create_with_create_with_has_higher_priority + new_comment = VerySpecialComment.where(:post_id => 2).create_with(:post_id => 1).scoping do + VerySpecialComment.create :body => "Wonderful world" + end + + assert_equal 1, new_comment.post_id + assert Post.find(1).comments.include?(new_comment) + end + + def test_ensure_that_method_scoping_is_correctly_restored + scoped_methods = Developer.send(:current_scoped_methods) + + begin + Developer.where("name = 'Jamis'").scoping do + raise "an exception" + end + rescue + end + + assert_equal scoped_methods, Developer.send(:current_scoped_methods) + end +end + +class NestedRelationScopingTest < ActiveRecord::TestCase + fixtures :authors, :developers, :projects, :comments, :posts + + def test_merge_options + Developer.where('salary = 80000').scoping do + Developer.limit(10).scoping do + devs = Developer.scoped + assert_equal '(salary = 80000)', devs.arel.send(:where_clauses).join(' AND ') + assert_equal 10, devs.taken + end + end + end + + def test_merge_inner_scope_has_priority + Developer.limit(5).scoping do + Developer.limit(10).scoping do + assert_equal 10, Developer.count + end + end + end + + def test_replace_options + Developer.where(:name => 'David').scoping do + Developer.unscoped do + assert_equal 'Jamis', Developer.where(:name => 'Jamis').first[:name] + end + + assert_equal 'David', Developer.first[:name] + end + end + + def test_three_level_nested_exclusive_scoped_find + Developer.where("name = 'Jamis'").scoping do + assert_equal 'Jamis', Developer.first.name + + Developer.unscoped.where("name = 'David'") do + assert_equal 'David', Developer.first.name + + Developer.unscoped.where("name = 'Maiha'") do + assert_equal nil, Developer.first + end + + # ensure that scoping is restored + assert_equal 'David', Developer.first.name + end + + # ensure that scoping is restored + assert_equal 'Jamis', Developer.first.name + end + end + + def test_nested_scoped_create + comment = Comment.create_with(:post_id => 1).scoping do + Comment.create_with(:post_id => 2).scoping do + Comment.create :body => "Hey guys, nested scopes are broken. Please fix!" + end + end + + assert_equal 2, comment.post_id + end + + def test_nested_exclusive_scope_for_create + comment = Comment.create_with(:body => "Hey guys, nested scopes are broken. Please fix!").scoping do + Comment.unscoped.create_with(:post_id => 1).scoping do + assert Comment.new.body.blank? + Comment.create :body => "Hey guys" + end + end + + assert_equal 1, comment.post_id + assert_equal 'Hey guys', comment.body + end +end + +class HasManyScopingTest< ActiveRecord::TestCase + fixtures :comments, :posts + + def setup + @welcome = Post.find(1) + end + + def test_forwarding_of_static_methods + assert_equal 'a comment...', Comment.what_are_you + assert_equal 'a comment...', @welcome.comments.what_are_you + end + + def test_forwarding_to_scoped + assert_equal 4, Comment.search_by_type('Comment').size + assert_equal 2, @welcome.comments.search_by_type('Comment').size + end + + def test_forwarding_to_dynamic_finders + assert_equal 4, Comment.find_all_by_type('Comment').size + assert_equal 2, @welcome.comments.find_all_by_type('Comment').size + end + + def test_nested_scope_finder + Comment.where('1=0').scoping do + assert_equal 0, @welcome.comments.count + assert_equal 'a comment...', @welcome.comments.what_are_you + end + + Comment.where('1=1').scoping do + assert_equal 2, @welcome.comments.count + assert_equal 'a comment...', @welcome.comments.what_are_you + end + end +end + +class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase + fixtures :posts, :categories, :categories_posts + + def setup + @welcome = Post.find(1) + end + + def test_forwarding_of_static_methods + assert_equal 'a category...', Category.what_are_you + assert_equal 'a category...', @welcome.categories.what_are_you + end + + def test_forwarding_to_dynamic_finders + assert_equal 4, Category.find_all_by_type('SpecialCategory').size + assert_equal 0, @welcome.categories.find_all_by_type('SpecialCategory').size + assert_equal 2, @welcome.categories.find_all_by_type('Category').size + end + + def test_nested_scope_finder + Category.where('1=0').scoping do + assert_equal 0, @welcome.categories.count + assert_equal 'a category...', @welcome.categories.what_are_you + end + + Category.where('1=1').scoping do + assert_equal 2, @welcome.categories.count + assert_equal 'a category...', @welcome.categories.what_are_you + end + end +end + +class DefaultScopingTest < ActiveRecord::TestCase + fixtures :developers, :posts + + def test_default_scope + expected = Developer.find(:all, :order => 'salary DESC').collect { |dev| dev.salary } + received = DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary } + assert_equal expected, received + end + + def test_default_scope_is_unscoped_on_find + assert_equal 1, DeveloperCalledDavid.count + assert_equal 11, DeveloperCalledDavid.unscoped.count + end + + def test_default_scope_is_unscoped_on_create + assert_nil DeveloperCalledJamis.unscoped.create!.name + end + + def test_default_scope_with_conditions_string + assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.find(:all).map(&:id).sort + assert_equal nil, DeveloperCalledDavid.create!.name + end + + def test_default_scope_with_conditions_hash + assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.find(:all).map(&:id).sort + assert_equal 'Jamis', DeveloperCalledJamis.create!.name + end + + def test_default_scoping_with_threads + 2.times do + Thread.new { assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values }.join + end + end + + def test_default_scoping_with_inheritance + # Inherit a class having a default scope and define a new default scope + klass = Class.new(DeveloperOrderedBySalary) + klass.send :default_scope, :limit => 1 + + # Scopes added on children should append to parent scope + assert_equal 1, klass.scoped.limit_value + assert_equal ['salary DESC'], klass.scoped.order_values + + # Parent should still have the original scope + assert_nil DeveloperOrderedBySalary.scoped.limit_value + assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values + end + + def test_default_scope_called_twice_merges_conditions + Developer.destroy_all + Developer.create!(:name => "David", :salary => 80000) + Developer.create!(:name => "David", :salary => 100000) + Developer.create!(:name => "Brian", :salary => 100000) + + klass = Class.new(Developer) + klass.__send__ :default_scope, :conditions => { :name => "David" } + klass.__send__ :default_scope, :conditions => { :salary => 100000 } + assert_equal 1, klass.count + assert_equal "David", klass.first.name + assert_equal 100000, klass.first.salary + end + def test_method_scope + expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary } + received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary } + assert_equal expected, received + end + + def test_nested_scope + expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary } + received = DeveloperOrderedBySalary.send(:with_scope, :find => { :order => 'name DESC'}) do + DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary } + end + assert_equal expected, received + end + + def test_named_scope_overwrites_default + expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.name } + received = DeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.name } + assert_equal expected, received + end + + def test_nested_exclusive_scope + expected = Developer.find(:all, :limit => 100).collect { |dev| dev.salary } + received = DeveloperOrderedBySalary.send(:with_exclusive_scope, :find => { :limit => 100 }) do + DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary } + end + assert_equal expected, received + end + + def test_overwriting_default_scope + expected = Developer.find(:all, :order => 'salary').collect { |dev| dev.salary } + received = DeveloperOrderedBySalary.find(:all, :order => 'salary').collect { |dev| dev.salary } + assert_equal expected, received + end + + def test_default_scope_using_relation + posts = PostWithComment.scoped + assert_equal 2, posts.count + assert_equal posts(:thinking), posts.first + end + + def test_create_attribute_overwrites_default_scoping + assert_equal 'David', PoorDeveloperCalledJamis.create!(:name => 'David').name + assert_equal 200000, PoorDeveloperCalledJamis.create!(:name => 'David', :salary => 200000).salary + end + + def test_create_attribute_overwrites_default_values + assert_equal nil, PoorDeveloperCalledJamis.create!(:salary => nil).salary + assert_equal 50000, PoorDeveloperCalledJamis.create!(:name => 'David').salary + end +end \ No newline at end of file diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index d27a69ea1cf..ffde8daa071 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -618,7 +618,7 @@ class RelationTest < ActiveRecord::TestCase end def test_anonymous_extension - relation = Post.where(:author_id => 1).order('id ASC').extend do + relation = Post.where(:author_id => 1).order('id ASC').extending do def author 'lifo' end diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index e35de3b9b01..de68fd7f24a 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -55,6 +55,12 @@ class Developer < ActiveRecord::Base def log=(message) audit_logs.build :message => message end + + def self.all_johns + self.with_exclusive_scope :find => where(:name => 'John') do + self.all + end + end end class AuditLog < ActiveRecord::Base @@ -103,4 +109,4 @@ end class PoorDeveloperCalledJamis < ActiveRecord::Base self.table_name = 'developers' default_scope :conditions => { :name => 'Jamis', :salary => 50000 } -end \ No newline at end of file +end diff --git a/activerecord/test/models/without_table.rb b/activerecord/test/models/without_table.rb new file mode 100644 index 00000000000..87f80911e1a --- /dev/null +++ b/activerecord/test/models/without_table.rb @@ -0,0 +1,3 @@ +class WithoutTable < ActiveRecord::Base + default_scope where(:published => true) +end \ No newline at end of file diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 59ad57ede8b..60586cf99a0 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,11 @@ *Rails 3.0.0 [Release Candidate] (unreleased)* +* Deprecation behavior is no longer hardcoded to the name of the environment. + Instead, it is set via config.active_support.deprecation and can be one + of :log, :stderr or :notify. :notify is a new style that sends the warning + via ActiveSupport::Notifications, and is the new default for production + [Yehuda Katz] + * Renamed ActiveSupport::Dependecies.load_(once_)paths to autoload_(once_)paths. [fxn] * Added ActiveSupport::FileUpdateChecker to execute a block only if a set of files changed, used by Router and I18n locale files. [José Valim] diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb index af277f99951..5b2cb6e3315 100644 --- a/activesupport/lib/active_support/core_ext/string/conversions.rb +++ b/activesupport/lib/active_support/core_ext/string/conversions.rb @@ -28,9 +28,8 @@ class String self[0] end unless method_defined?(:ord) - def getbyte(index) - self[index] - end if RUBY_VERSION < '1.9' + # +getbyte+ backport from Ruby 1.9 + alias_method :getbyte, :[] unless method_defined?(:getbyte) # Form can be either :utc (default) or :local. def to_time(form = :utc) diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb index 578c025fcf6..feb15085863 100644 --- a/activesupport/lib/active_support/deprecation/behaviors.rb +++ b/activesupport/lib/active_support/deprecation/behaviors.rb @@ -1,28 +1,27 @@ +require "active_support/notifications" + module ActiveSupport module Deprecation class << self - # Behavior is a block that takes a message argument. - attr_writer :behavior - # Whether to print a backtrace along with the warning. attr_accessor :debug def behavior - @behavior ||= default_behavior + @behavior ||= DEFAULT_BEHAVIORS[:stderr] end - def default_behavior - Deprecation::DEFAULT_BEHAVIORS[defined?(Rails.env) ? Rails.env.to_s : 'test'] + def behavior=(behavior) + @behavior = DEFAULT_BEHAVIORS[behavior] || behavior end end - # Default warning behaviors per Rails.env. Ignored in production. + # Default warning behaviors per Rails.env. DEFAULT_BEHAVIORS = { - 'test' => Proc.new { |message, callstack| + :stderr => Proc.new { |message, callstack| $stderr.puts(message) $stderr.puts callstack.join("\n ") if debug }, - 'development' => Proc.new { |message, callstack| + :log => Proc.new { |message, callstack| logger = if defined?(Rails) && Rails.logger Rails.logger @@ -32,6 +31,10 @@ module ActiveSupport end logger.warn message logger.debug callstack.join("\n ") if debug + }, + :notify => Proc.new { |message, callstack| + ActiveSupport::Notifications.instrument("deprecation.rails", + :message => message, :callstack => callstack) } } end diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index c107aad6bb1..8823e4a5ed5 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -105,7 +105,7 @@ module ActiveSupport #:nodoc: # Example: # ('Café'.mb_chars + ' périferôl').to_s #=> "Café périferôl" def +(other) - self << other + chars(@wrapped_string + other) end # Like String#=~ only it returns the character offset (in codepoints) instead of the byte offset. @@ -316,11 +316,12 @@ module ActiveSupport #:nodoc: result = @wrapped_string.slice(*args) elsif args.size == 1 && args[0].kind_of?(Numeric) character = Unicode.u_unpack(@wrapped_string)[args[0]] - result = character.nil? ? nil : [character].pack('U') + result = character && [character].pack('U') else - result = Unicode.u_unpack(@wrapped_string).slice(*args).pack('U*') + cps = Unicode.u_unpack(@wrapped_string).slice(*args) + result = cps && cps.pack('U*') end - result.nil? ? nil : chars(result) + result && chars(result) end alias_method :[], :slice diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index f91e50c7553..11c72d873b2 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -9,7 +9,7 @@ module ActiveSupport NORMALIZATION_FORMS = [:c, :kc, :d, :kd] # The Unicode version that is supported by the implementation - UNICODE_VERSION = '5.1.0' + UNICODE_VERSION = '5.2.0' # The default normalization used for operations that require normalization. It can be set to any of the # normalizations in NORMALIZATION_FORMS. diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb index 1f32f8718fe..c2deba3b1bd 100644 --- a/activesupport/lib/active_support/railtie.rb +++ b/activesupport/lib/active_support/railtie.rb @@ -12,6 +12,36 @@ module ActiveSupport require 'active_support/whiny_nil' if app.config.whiny_nils end + initializer "active_support.deprecation_behavior" do |app| + if deprecation = app.config.active_support.deprecation + ActiveSupport::Deprecation.behavior = deprecation + else + defaults = {"development" => :log, + "production" => :notify, + "test" => :stderr} + + env = Rails.env + + if defaults.key?(env) + msg = "You did not specify how you would like Rails to report " \ + "deprecation notices for your #{env} environment, please " \ + "set config.active_support.deprecation to :#{defaults[env]} " \ + "at config/environments/#{env}.rb" + + warn msg + ActiveSupport::Deprecation.behavior = defaults[env] + else + msg = "You did not specify how you would like Rails to report " \ + "deprecation notices for your #{env} environment, please " \ + "set config.active_support.deprecation to :log, :notify or " \ + ":stderr at config/environments/#{env}.rb" + + warn msg + ActiveSupport::Deprecation.behavior = :stderr + end + end + end + # Sets the default value for Time.zone # If assigned value cannot be matched to a TimeZone, an exception will be raised. initializer "active_support.initialize_time_zone" do |app| @@ -27,4 +57,4 @@ module ActiveSupport Time.zone_default = zone_default end end -end \ No newline at end of file +end diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb index cd628a956dc..a124c6e0ca2 100644 --- a/activesupport/lib/active_support/testing/performance.rb +++ b/activesupport/lib/active_support/testing/performance.rb @@ -260,16 +260,14 @@ begin end protected - # Ruby 1.9 + extented GC profiler patch - if defined?(GC::Profiler) and GC::Profiler.respond_to?(:data) + # Ruby 1.9 with GC::Profiler + if defined?(GC::Profiler) def with_gc_stats - GC.start - GC.disable GC::Profiler.enable + GC.start yield ensure GC::Profiler.disable - GC.enable end # Ruby 1.8 + ruby-prof wrapper (enable/disable stats for Benchmarker) @@ -294,7 +292,7 @@ begin end def format(measurement) - if measurement < 2 + if measurement < 1 '%d ms' % (measurement * 1000) else '%.2f sec' % measurement @@ -335,14 +333,10 @@ begin class Memory < Base Mode = RubyProf::MEMORY if RubyProf.const_defined?(:MEMORY) - # Ruby 1.9 + extended GC profiler patch - if defined?(GC::Profiler) and GC::Profiler.respond_to?(:data) + # Ruby 1.9 + GCdata patch + if GC.respond_to?(:malloc_allocated_size) def measure - GC.enable - GC.start - kb = GC::Profiler.data.last[:HEAP_USE_SIZE] / 1024.0 - GC.disable - kb + GC.malloc_allocated_size / 1024.0 end # Ruby 1.8 + ruby-prof wrapper @@ -360,14 +354,10 @@ begin class Objects < Base Mode = RubyProf::ALLOCATIONS if RubyProf.const_defined?(:ALLOCATIONS) - # Ruby 1.9 + extented GC profiler patch - if defined?(GC::Profiler) and GC::Profiler.respond_to?(:data) + # Ruby 1.9 + GCdata patch + if GC.respond_to?(:malloc_allocations) def measure - GC.enable - GC.start - count = GC::Profiler.data.last[:HEAP_TOTAL_OBJECTS] - GC.disable - count + GC.malloc_allocations end # Ruby 1.8 + ruby-prof wrapper @@ -385,14 +375,10 @@ begin class GcRuns < Base Mode = RubyProf::GC_RUNS if RubyProf.const_defined?(:GC_RUNS) - # Ruby 1.9 + extented GC profiler patch - if defined?(GC::Profiler) and GC::Profiler.respond_to?(:data) + # Ruby 1.9 + if GC.respond_to?(:count) def measure - GC.enable - GC.start - count = GC::Profiler.data.last[:GC_RUNS] - GC.disable - count + GC.count end # Ruby 1.8 + ruby-prof wrapper @@ -410,25 +396,21 @@ begin class GcTime < Base Mode = RubyProf::GC_TIME if RubyProf.const_defined?(:GC_TIME) - # Ruby 1.9 + extented GC profiler patch - if defined?(GC::Profiler) and GC::Profiler.respond_to?(:data) + # Ruby 1.9 with GC::Profiler + if GC.respond_to?(:total_time) def measure - GC.enable - GC.start - sec = GC::Profiler.data.inject(0) { |total, run| total += run[:GC_TIME] } - GC.disable - sec + GC::Profiler.total_time end # Ruby 1.8 + ruby-prof wrapper elsif RubyProf.respond_to?(:measure_gc_time) def measure - RubyProf.measure_gc_time + RubyProf.measure_gc_time / 1000 end end def format(measurement) - '%d ms' % (measurement / 1000) + '%.2f ms' % measurement end end end diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 0a08016522d..05b40298d30 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -350,7 +350,11 @@ module ActiveSupport def [](arg) case arg when String - zones_map[arg] ||= lookup(arg) + begin + zones_map[arg] ||= lookup(arg).tap { |tz| tz.utc_offset } + rescue TZInfo::InvalidTimezoneIdentifier + nil + end when Numeric, ActiveSupport::Duration arg *= 3600 if arg.abs <= 13 all.find { |z| z.utc_offset == arg.to_i } diff --git a/activesupport/lib/active_support/values/unicode_tables.dat b/activesupport/lib/active_support/values/unicode_tables.dat index 10f2cae4655..4fe0268cca0 100644 Binary files a/activesupport/lib/active_support/values/unicode_tables.dat and b/activesupport/lib/active_support/values/unicode_tables.dat differ diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index cf11f4d28f7..5ce42776726 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -832,9 +832,7 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase def test_time_zone_setter_with_invalid_zone Time.zone = 'foo' - assert_not_nil Time.zone - assert_equal 'foo', Time.zone.name - assert_raise(TZInfo::InvalidTimezoneIdentifier) { Time.zone.utc_offset } + assert_nil Time.zone Time.zone = -15.hours assert_nil Time.zone diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb index 602828ef5f8..610295fa890 100644 --- a/activesupport/test/multibyte_chars_test.rb +++ b/activesupport/test/multibyte_chars_test.rb @@ -49,13 +49,15 @@ class MultibyteCharsTest < Test::Unit::TestCase end def test_should_concatenate - assert_equal 'ab', 'a'.mb_chars + 'b' - assert_equal 'ab', 'a' + 'b'.mb_chars - assert_equal 'ab', 'a'.mb_chars + 'b'.mb_chars + mb_a = 'a'.mb_chars + mb_b = 'b'.mb_chars + assert_equal 'ab', mb_a + 'b' + assert_equal 'ab', 'a' + mb_b + assert_equal 'ab', mb_a + mb_b - assert_equal 'ab', 'a'.mb_chars << 'b' - assert_equal 'ab', 'a' << 'b'.mb_chars - assert_equal 'ab', 'a'.mb_chars << 'b'.mb_chars + assert_equal 'ab', mb_a << 'b' + assert_equal 'ab', 'a' << mb_b + assert_equal 'abb', mb_a << mb_b end def test_consumes_utf8_strings @@ -395,6 +397,7 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase assert_equal 'こ', @chars.slice(0) assert_equal 'わ', @chars.slice(3) assert_equal nil, ''.mb_chars.slice(-1..1) + assert_equal nil, ''.mb_chars.slice(-1, 1) assert_equal '', ''.mb_chars.slice(0..10) assert_equal 'にちわ', @chars.slice(1..3) assert_equal 'にちわ', @chars.slice(1, 3) diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index 620623b3890..af6eee69e5e 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -268,7 +268,7 @@ class TimeZoneTest < Test::Unit::TestCase end def test_index - assert_not_nil ActiveSupport::TimeZone["bogus"] + assert_nil ActiveSupport::TimeZone["bogus"] assert_instance_of ActiveSupport::TimeZone, ActiveSupport::TimeZone["Central Time (US & Canada)"] assert_instance_of ActiveSupport::TimeZone, ActiveSupport::TimeZone[8] assert_raise(ArgumentError) { ActiveSupport::TimeZone[false] } @@ -285,6 +285,11 @@ class TimeZoneTest < Test::Unit::TestCase assert_equal(-21_600, zone.utc_offset) end + def test_unknown_zones_dont_store_mapping_keys + ActiveSupport::TimeZone["bogus"] + assert !ActiveSupport::TimeZone.zones_map.key?("bogus") + end + def test_new assert_equal ActiveSupport::TimeZone["Central Time (US & Canada)"], ActiveSupport::TimeZone.new("Central Time (US & Canada)") end diff --git a/railties/CHANGELOG b/railties/CHANGELOG index 61d84e104ae..8e2bac7f006 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,5 +1,9 @@ *Rails 3.0.0 [Release Candidate] (unreleased)* +* Rails no longer autoload code in lib for application. You need to explicitly require it. [José Valim] + +* Rails::LogSubscriber was renamed to ActiveSupport::LogSubscriber [José Valim] + * config.load_(once_)paths in config/application.rb got renamed to config.autoload_(once_)paths. [fxn] * Abort generation/booting on Ruby 1.9.1. [fxn] diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index aabe86715c1..4a7ed2d028d 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -28,7 +28,7 @@ module Rails # Besides providing the same configuration as Rails::Engine and Rails::Railtie, # the application object has several specific configurations, for example # "allow_concurrency", "cache_classes", "consider_all_requests_local", "filter_parameters", - # "logger", "reload_engines", "reload_plugins" and so forth. + # "logger", "reload_plugins" and so forth. # # Check Rails::Application::Configuration to see them all. # @@ -217,10 +217,5 @@ module Rails def initialize_generators require "rails/generators" end - - # Application is always reloadable when config.cache_classes is false. - def reloadable?(app) - true - end end end diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index e3165b2d4ca..465851c0e63 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -10,7 +10,7 @@ module Rails attr_accessor :allow_concurrency, :cache_classes, :cache_store, :encoding, :consider_all_requests_local, :dependency_loading, :filter_parameters, :log_level, :logger, :middleware, - :plugins, :preload_frameworks, :reload_engines, :reload_plugins, + :plugins, :preload_frameworks, :reload_plugins, :secret_token, :serve_static_assets, :session_options, :time_zone, :whiny_nils @@ -59,7 +59,7 @@ module Rails if File.exists?("#{root}/test/mocks/#{Rails.env}") ActiveSupport::Deprecation.warn "\"Rails.root/test/mocks/#{Rails.env}\" won't be added " << "automatically to load paths anymore in future releases" - paths.mocks_path "test/mocks", :load_path => true, :glob => Rails.env + paths.mocks_path "test/mocks", :autoload => true, :glob => Rails.env end paths diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 0a3f21fb1ba..ee3e3ba0403 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -142,7 +142,7 @@ module Rails # Add configured load paths to ruby load paths and remove duplicates. initializer :set_load_path, :before => :bootstrap_hook do - config.autoload_paths.reverse_each do |path| + _all_load_paths.reverse_each do |path| $LOAD_PATH.unshift(path) if File.directory?(path) end $LOAD_PATH.uniq! @@ -154,16 +154,12 @@ module Rails # This needs to be an initializer, since it needs to run once # per engine and get the engine as a block parameter initializer :set_autoload_paths, :before => :bootstrap_hook do |app| - ActiveSupport::Dependencies.autoload_paths.unshift(*config.autoload_paths) - - if reloadable?(app) - ActiveSupport::Dependencies.autoload_once_paths.unshift(*config.autoload_once_paths) - else - ActiveSupport::Dependencies.autoload_once_paths.unshift(*config.autoload_paths) - end + ActiveSupport::Dependencies.autoload_paths.unshift(*_all_autoload_paths) + ActiveSupport::Dependencies.autoload_once_paths.unshift(*config.autoload_once_paths) # Freeze so future modifications will fail rather than do nothing mysteriously config.autoload_paths.freeze + config.eager_load_paths.freeze config.autoload_once_paths.freeze end @@ -195,7 +191,6 @@ module Rails ActiveSupport.on_load(:action_controller) do prepend_view_path(views) end - ActiveSupport.on_load(:action_mailer) do prepend_view_path(views) end @@ -214,8 +209,12 @@ module Rails protected - def reloadable?(app) - app.config.reload_engines + def _all_autoload_paths + @_all_autoload_paths ||= (config.autoload_paths + config.eager_load_paths + config.autoload_once_paths).uniq + end + + def _all_load_paths + @_all_load_paths ||= (config.paths.load_paths + _all_autoload_paths).uniq end end end diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index 4e27180f1e8..2f465670cf6 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -42,11 +42,11 @@ module Rails end def autoload_once_paths - @autoload_once_paths ||= paths.load_once + @autoload_once_paths ||= paths.autoload_once end def autoload_paths - @autoload_paths ||= paths.load_paths + @autoload_paths ||= paths.autoload_paths end end end diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index 7af329bbf0d..199afbdc304 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -275,7 +275,7 @@ module Rails # def route(routing_code) log :route, routing_code - sentinel = "routes.draw do |map|" + sentinel = /\.routes\.draw do(\s*\|map\|)?\s*$/ in_root do inject_into_file 'config/routes.rb', "\n #{routing_code}\n", { :after => sentinel, :verbose => false } diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index f0e917dd96f..99758dfcf7f 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -16,4 +16,7 @@ # Don't care if the mailer can't send config.action_mailer.raise_delivery_errors = false + + # Print deprecation notices to the Rails logger + config.active_support.deprecation = :log end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index b9fb13b6409..500fc4d860f 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -43,4 +43,7 @@ # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation can not be found) config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners + config.active_support.deprecation = :notify end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt index beb28e22293..26cdef071a0 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt @@ -29,4 +29,7 @@ # This is necessary if your schema can't be completely dumped by the schema dumper, # like if you have constraints or database-specific column types # config.active_record.schema_format = :sql + + # Print deprecation notices to the stderr + config.active_support.deprecation = :stderr end diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb index d6c0365c04c..d42cf3bfdfb 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb @@ -1,4 +1,4 @@ -<%= app_const %>.routes.draw do |map| +<%= app_const %>.routes.draw do # The priority is based upon order of creation: # first created -> highest priority. diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/rails.js b/railties/lib/rails/generators/rails/app/templates/public/javascripts/rails.js index c5fa02ae359..4283ed89824 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/rails.js +++ b/railties/lib/rails/generators/rails/app/templates/public/javascripts/rails.js @@ -1,87 +1,148 @@ -document.observe("dom:loaded", function() { +(function() { + // Technique from Juriy Zaytsev + // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ + function isEventSupported(eventName) { + var el = document.createElement('div'); + eventName = 'on' + eventName; + var isSupported = (eventName in el); + if (!isSupported) { + el.setAttribute(eventName, 'return;'); + isSupported = typeof el[eventName] == 'function'; + } + el = null; + return isSupported; + } + + function isForm(element) { + return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM' + } + + function isInput(element) { + if (Object.isElement(element)) { + var name = element.nodeName.toUpperCase() + return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA' + } + else return false + } + + var submitBubbles = isEventSupported('submit'), + changeBubbles = isEventSupported('change') + + if (!submitBubbles || !changeBubbles) { + // augment the Event.Handler class to observe custom events when needed + Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap( + function(init, element, eventName, selector, callback) { + init(element, eventName, selector, callback) + // is the handler being attached to an element that doesn't support this event? + if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) || + (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) { + // "submit" => "emulated:submit" + this.eventName = 'emulated:' + this.eventName + } + } + ) + } + + if (!submitBubbles) { + // discover forms on the page by observing focus events which always bubble + document.on('focusin', 'form', function(focusEvent, form) { + // special handler for the real "submit" event (one-time operation) + if (!form.retrieve('emulated:submit')) { + form.on('submit', function(submitEvent) { + var emulated = form.fire('emulated:submit', submitEvent, true) + // if custom event received preventDefault, cancel the real one too + if (emulated.returnValue === false) submitEvent.preventDefault() + }) + form.store('emulated:submit', true) + } + }) + } + + if (!changeBubbles) { + // discover form inputs on the page + document.on('focusin', 'input, select, texarea', function(focusEvent, input) { + // special handler for real "change" events + if (!input.retrieve('emulated:change')) { + input.on('change', function(changeEvent) { + input.fire('emulated:change', changeEvent, true) + }) + input.store('emulated:change', true) + } + }) + } + function handleRemote(element) { var method, url, params; + var event = element.fire("ajax:before"); + if (event.stopped) return false; + if (element.tagName.toLowerCase() === 'form') { method = element.readAttribute('method') || 'post'; url = element.readAttribute('action'); - params = element.serialize(true); + params = element.serialize(); } else { method = element.readAttribute('data-method') || 'get'; url = element.readAttribute('href'); params = {}; } - var event = element.fire("ajax:before"); - if (event.stopped) return false; - new Ajax.Request(url, { method: method, parameters: params, - asynchronous: true, evalScripts: true, - onLoading: function(request) { element.fire("ajax:loading", {request: request}); }, - onLoaded: function(request) { element.fire("ajax:loaded", {request: request}); }, - onInteractive: function(request) { element.fire("ajax:interactive", {request: request}); }, - onComplete: function(request) { element.fire("ajax:complete", {request: request}); }, - onSuccess: function(request) { element.fire("ajax:success", {request: request}); }, - onFailure: function(request) { element.fire("ajax:failure", {request: request}); } + onComplete: function(request) { element.fire("ajax:complete", request); }, + onSuccess: function(request) { element.fire("ajax:success", request); }, + onFailure: function(request) { element.fire("ajax:failure", request); } }); element.fire("ajax:after"); } function handleMethod(element) { - var method, url, token_name, token; - - method = element.readAttribute('data-method'); - url = element.readAttribute('href'); - csrf_param = $$('meta[name=csrf-param]').first(); - csrf_token = $$('meta[name=csrf-token]').first(); + var method = element.readAttribute('data-method'), + url = element.readAttribute('href'), + csrf_param = $$('meta[name=csrf-param]')[0], + csrf_token = $$('meta[name=csrf-token]')[0]; var form = new Element('form', { method: "POST", action: url, style: "display: none;" }); - element.parentNode.appendChild(form); + element.parentNode.insert(form); - if (method != 'post') { + if (method !== 'post') { var field = new Element('input', { type: 'hidden', name: '_method', value: method }); - form.appendChild(field); + form.insert(field); } if (csrf_param) { - var param = csrf_param.readAttribute('content'); - var token = csrf_token.readAttribute('content'); - var field = new Element('input', { type: 'hidden', name: param, value: token }); - form.appendChild(field); + var param = csrf_param.readAttribute('content'), + token = csrf_token.readAttribute('content'), + field = new Element('input', { type: 'hidden', name: param, value: token }); + form.insert(field); } form.submit(); } - $(document.body).observe("click", function(event) { - var message = event.findElement().readAttribute('data-confirm'); - if (message && !confirm(message)) { - event.stop(); - return false; - } - var element = event.findElement("a[data-remote]"); - if (element) { - handleRemote(element); - event.stop(); - return true; - } - - var element = event.findElement("a[data-method]"); - if (element) { - handleMethod(element); - event.stop(); - return true; - } + document.on("click", "*[data-confirm]", function(event, element) { + var message = element.readAttribute('data-confirm'); + if (!confirm(message)) event.stop(); }); - // TODO: I don't think submit bubbles in IE - $(document.body).observe("submit", function(event) { + document.on("click", "a[data-remote]", function(event, element) { + if (event.stopped) return; + handleRemote(element); + event.stop(); + }); + + document.on("click", "a[data-method]", function(event, element) { + if (event.stopped) return; + handleMethod(element); + event.stop(); + }); + + document.on("submit", function(event) { var element = event.findElement(), message = element.readAttribute('data-confirm'); if (message && !confirm(message)) { @@ -103,16 +164,12 @@ document.observe("dom:loaded", function() { } }); - $(document.body).observe("ajax:after", function(event) { - var element = event.findElement(); - - if (element.tagName.toLowerCase() === 'form') { - var inputs = element.select("input[type=submit][disabled=true][data-disable-with]"); - inputs.each(function(input) { - input.value = input.readAttribute('data-original-value'); - input.writeAttribute('data-original-value', null); - input.disabled = false; - }); - } + document.on("ajax:after", "form", function(event, element) { + var inputs = element.select("input[type=submit][disabled=true][data-disable-with]"); + inputs.each(function(input) { + input.value = input.readAttribute('data-original-value'); + input.removeAttribute('data-original-value'); + input.disabled = false; + }); }); -}); \ No newline at end of file +})(); diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb index 1c9e3086314..7a65188a9ab 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -25,9 +25,7 @@ module Rails def initialize(path) raise if path.is_a?(Array) - @children = {} - @path = path @root = self @all_paths = [] @@ -38,14 +36,18 @@ module Rails @all_paths end - def load_once - filter_by(:load_once?) + def autoload_once + filter_by(:autoload_once?) end def eager_load filter_by(:eager_load?) end + def autoload_paths + filter_by(:autoload?) + end + def load_paths filter_by(:load_path?) end @@ -61,15 +63,17 @@ module Rails protected def filter_by(constraint) - all_paths.map do |path| + all = [] + all_paths.each do |path| if path.send(constraint) paths = path.paths paths -= path.children.values.map { |p| p.send(constraint) ? [] : p.paths }.flatten - paths - else - [] + all.concat(paths) end - end.flatten.uniq.select { |p| File.exists?(p) } + end + all.uniq! + all.reject! { |p| !File.exists?(p) } + all end end @@ -80,15 +84,16 @@ module Rails attr_accessor :glob def initialize(root, *paths) - @options = paths.last.is_a?(::Hash) ? paths.pop : {} + options = paths.last.is_a?(::Hash) ? paths.pop : {} @children = {} @root = root @paths = paths.flatten - @glob = @options.delete(:glob) + @glob = options[:glob] - @load_once = @options[:load_once] - @eager_load = @options[:eager_load] - @load_path = @options[:load_path] || @eager_load || @load_once + autoload_once! if options[:autoload_once] + eager_load! if options[:eager_load] + autoload! if options[:autoload] + load_path! if options[:load_path] @root.all_paths << self end @@ -111,24 +116,30 @@ module Rails @paths.concat paths end - def load_once! - @load_once = true - @load_path = true + def autoload_once! + @autoload_once = true end - def load_once? - @load_once + def autoload_once? + @autoload_once end def eager_load! @eager_load = true - @load_path = true end def eager_load? @eager_load end + def autoload! + @autoload = true + end + + def autoload? + @autoload + end + def load_path! @load_path = true end diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb index fcdd0991359..be229cc9a20 100644 --- a/railties/lib/rails/plugin.rb +++ b/railties/lib/rails/plugin.rb @@ -61,6 +61,16 @@ module Rails @config ||= Engine::Configuration.new end + initializer :handle_lib_autoload, :before => :set_load_path do |app| + paths = if app.config.reload_plugins + config.autoload_paths + else + config.autoload_once_paths + end + + paths.concat config.paths.lib.to_a + end + initializer :load_init_rb, :before => :load_config_initializers do |app| files = %w(rails/init.rb init.rb).map { |path| File.expand_path path, root } if initrb = files.find { |path| File.file? path } @@ -77,11 +87,5 @@ module Rails raise "\"#{name}\" is a Railtie/Engine and cannot be installed as plugin" end end - - protected - - def reloadable?(app) - app.config.reload_plugins - end end end diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb index b337d3fc6e6..340ce675112 100644 --- a/railties/test/application/loading_test.rb +++ b/railties/test/application/loading_test.rb @@ -12,7 +12,7 @@ class LoadingTest < Test::Unit::TestCase @app ||= Rails.application end - def test_load_should_load_constants + def test_constants_in_app_are_autoloaded app_file "app/models/post.rb", <<-MODEL class Post < ActiveRecord::Base validates_acceptance_of :title, :accept => "omg" @@ -29,6 +29,19 @@ class LoadingTest < Test::Unit::TestCase assert_equal 'omg', p.title end + def test_models_without_table_do_not_panic_on_scope_definitions_when_loaded + app_file "app/models/user.rb", <<-MODEL + class User < ActiveRecord::Base + default_scope where(:published => true) + end + MODEL + + require "#{rails_root}/config/environment" + setup_ar! + + User + end + def test_descendants_are_cleaned_on_each_request_without_cache_classes add_to_config <<-RUBY config.cache_classes = false diff --git a/railties/test/application/url_generation_test.rb b/railties/test/application/url_generation_test.rb index 72cae23985a..2b6ec26cd0a 100644 --- a/railties/test/application/url_generation_test.rb +++ b/railties/test/application/url_generation_test.rb @@ -16,6 +16,7 @@ module ApplicationTests class MyApp < Rails::Application config.secret_token = "3b7cd727ee24e8444053437c36cc66c4" config.session_store :cookie_store, :key => "_myapp_session" + config.active_support.deprecation = :log end MyApp.initialize! diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index b46ac0efafa..390c0ab543e 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -100,7 +100,7 @@ module TestHelpers end end - add_to_config 'config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"; config.session_store :cookie_store, :key => "_myapp_session"' + add_to_config 'config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"; config.session_store :cookie_store, :key => "_myapp_session"; config.active_support.deprecation = :log' end def make_basic_app @@ -110,6 +110,7 @@ module TestHelpers app = Class.new(Rails::Application) app.config.secret_token = "3b7cd727ee24e8444053437c36cc66c4" app.config.session_store :cookie_store, :key => "_myapp_session" + app.config.active_support.deprecation = :log yield app if block_given? app.initialize! diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb index 92c7b2ba0e2..008247cb1aa 100644 --- a/railties/test/paths_test.rb +++ b/railties/test/paths_test.rb @@ -120,36 +120,36 @@ class PathsTest < ActiveSupport::TestCase test "it is possible to add a path that should be loaded only once" do @root.app = "/app" - @root.app.load_once! - assert @root.app.load_once? - assert @root.load_once.include?(@root.app.paths.first) + @root.app.autoload_once! + assert @root.app.autoload_once? + assert @root.autoload_once.include?(@root.app.paths.first) end test "it is possible to add a path without assignment and specify it should be loaded only once" do - @root.app "/app", :load_once => true - assert @root.app.load_once? - assert @root.load_once.include?("/app") + @root.app "/app", :autoload_once => true + assert @root.app.autoload_once? + assert @root.autoload_once.include?("/app") end test "it is possible to add multiple paths without assignment and specify it should be loaded only once" do - @root.app "/app", "/app2", :load_once => true - assert @root.app.load_once? - assert @root.load_once.include?("/app") - assert @root.load_once.include?("/app2") + @root.app "/app", "/app2", :autoload_once => true + assert @root.app.autoload_once? + assert @root.autoload_once.include?("/app") + assert @root.autoload_once.include?("/app2") end - test "making a path load_once more than once only includes it once in @root.load_once" do + test "making a path autoload_once more than once only includes it once in @root.load_once" do @root.app = "/app" - @root.app.load_once! - @root.app.load_once! - assert_equal 1, @root.load_once.select {|p| p == @root.app.paths.first }.size + @root.app.autoload_once! + @root.app.autoload_once! + assert_equal 1, @root.autoload_once.select {|p| p == @root.app.paths.first }.size end - test "paths added to a load_once path should be added to the load_once collection" do + test "paths added to a load_once path should be added to the autoload_once collection" do @root.app = "/app" - @root.app.load_once! + @root.app.autoload_once! @root.app << "/app2" - assert_equal 2, @root.load_once.size + assert_equal 2, @root.autoload_once.size end test "it is possible to mark a path as eager" do @@ -173,11 +173,11 @@ class PathsTest < ActiveSupport::TestCase end test "it is possible to create a path without assignment and mark it both as eager and load once" do - @root.app "/app", :eager_load => true, :load_once => true + @root.app "/app", :eager_load => true, :autoload_once => true assert @root.app.eager_load? - assert @root.app.load_once? + assert @root.app.autoload_once? assert @root.eager_load.include?("/app") - assert @root.load_once.include?("/app") + assert @root.autoload_once.include?("/app") end test "making a path eager more than once only includes it once in @root.eager_paths" do @@ -218,16 +218,16 @@ class PathsTest < ActiveSupport::TestCase assert_equal ["/app"], @root.load_paths end - test "adding a path to the eager paths also adds it to the load path" do + test "a path can be marked as autoload path" do @root.app = "app" - @root.app.eager_load! - assert_equal ["/foo/bar/app"], @root.load_paths + @root.app.autoload! + @root.app.models = "app/models" + assert_equal ["/foo/bar/app"], @root.autoload_paths end - test "adding a path to the load once paths also adds it to the load path" do - @root.app = "app" - @root.app.load_once! - assert_equal ["/foo/bar/app"], @root.load_paths + test "a path can be marked as autoload on creation" do + @root.app "/app", :autoload => true + assert @root.app.autoload? + assert_equal ["/app"], @root.autoload_paths end - end diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 3fe01e543c3..7410a10712e 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -20,10 +20,6 @@ module RailtiesTest end end - def reload_config - :reload_engines - end - test "Rails::Engine itself does not respond to config" do boot_rails assert !Rails::Engine.respond_to?(:config) diff --git a/railties/test/railties/plugin_test.rb b/railties/test/railties/plugin_test.rb index 0f5f29468c1..b143f564847 100644 --- a/railties/test/railties/plugin_test.rb +++ b/railties/test/railties/plugin_test.rb @@ -15,10 +15,6 @@ module RailtiesTest end end - def reload_config - :reload_plugins - end - test "Rails::Plugin itself does not respond to config" do boot_rails assert !Rails::Plugin.respond_to?(:config) @@ -37,6 +33,32 @@ module RailtiesTest assert_equal "Bukkits", Bukkits.name end + test "plugin gets added to dependency list" do + boot_rails + assert_equal "Another", Another.name + end + + test "plugin constants get reloaded if config.reload_plugins is set to true" do + add_to_config <<-RUBY + config.reload_plugins = true + RUBY + + boot_rails + + assert_equal "Another", Another.name + ActiveSupport::Dependencies.clear + @plugin.delete("lib/another.rb") + assert_raises(NameError) { Another } + end + + test "plugin constants are not reloaded by default" do + boot_rails + assert_equal "Another", Another.name + ActiveSupport::Dependencies.clear + @plugin.delete("lib/another.rb") + assert_nothing_raised { Another } + end + test "it loads the plugin's init.rb file" do boot_rails assert_equal "loaded", BUKKITS diff --git a/railties/test/railties/shared_tests.rb b/railties/test/railties/shared_tests.rb index 3f78d7d3fe0..ce7c55c11c6 100644 --- a/railties/test/railties/shared_tests.rb +++ b/railties/test/railties/shared_tests.rb @@ -10,51 +10,25 @@ module RailtiesTest @app ||= Rails.application end - def test_plugin_puts_its_lib_directory_on_load_path + def test_puts_its_lib_directory_on_load_path boot_rails require "another" assert_equal "Another", Another.name end - def test_plugin_paths_get_added_to_as_dependency_list - boot_rails - assert_equal "Another", Another.name - end - - def test_plugins_constants_are_not_reloaded_by_default - boot_rails - assert_equal "Another", Another.name - ActiveSupport::Dependencies.clear - @plugin.delete("lib/another.rb") - assert_nothing_raised { Another } - end - - def test_plugin_constants_get_reloaded_if_config_reload_plugins - add_to_config <<-RUBY - config.#{reload_config} = true - RUBY - - boot_rails - - assert_equal "Another", Another.name - ActiveSupport::Dependencies.clear - @plugin.delete("lib/another.rb") - assert_raises(NameError) { Another } - end - - def test_plugin_puts_its_models_directory_on_load_path + def test_puts_its_models_directory_on_autoload_path @plugin.write "app/models/my_bukkit.rb", "class MyBukkit ; end" boot_rails assert_nothing_raised { MyBukkit } end - def test_plugin_puts_its_controllers_directory_on_the_load_path + def test_puts_its_controllers_directory_on_autoload_path @plugin.write "app/controllers/bukkit_controller.rb", "class BukkitController ; end" boot_rails assert_nothing_raised { BukkitController } end - def test_plugin_adds_its_views_to_view_paths + def test_adds_its_views_to_view_paths @plugin.write "app/controllers/bukkit_controller.rb", <<-RUBY class BukkitController < ActionController::Base def index @@ -72,7 +46,7 @@ module RailtiesTest assert_equal "Hello bukkits\n", response[2].body end - def test_plugin_adds_its_views_to_view_paths_with_lower_proriority + def test_adds_its_views_to_view_paths_with_lower_proriority_than_app_ones @plugin.write "app/controllers/bukkit_controller.rb", <<-RUBY class BukkitController < ActionController::Base def index @@ -91,7 +65,7 @@ module RailtiesTest assert_equal "Hi bukkits\n", response[2].body end - def test_plugin_adds_helpers_to_controller_views + def test_adds_helpers_to_controller_views @plugin.write "app/controllers/bukkit_controller.rb", <<-RUBY class BukkitController < ActionController::Base def index @@ -116,11 +90,10 @@ module RailtiesTest assert_equal "Hello bukkits\n", response[2].body end - def test_plugin_eager_load_any_path_under_app + def test_autoload_any_path_under_app @plugin.write "app/anything/foo.rb", <<-RUBY module Foo; end RUBY - boot_rails assert Foo end @@ -269,7 +242,7 @@ YAML assert_equal "Rendered from namespace", last_response.body end - def test_plugin_initializers + def test_initializers $plugin_initializer = false @plugin.write "config/initializers/foo.rb", <<-RUBY $plugin_initializer = true @@ -279,7 +252,7 @@ YAML assert $plugin_initializer end - def test_plugin_midleware_referenced_in_configuration + def test_midleware_referenced_in_configuration @plugin.write "lib/bukkits.rb", <<-RUBY class Bukkits def initialize(app)