diff options
28 files changed, 251 insertions, 183 deletions
@@ -1,3 +1,4 @@ --exclude /templates/ --quiet act*/lib/**/*.rb +railties/lib/**/*.rb @@ -13,6 +13,11 @@ gem "mocha", ">= 0.9.8" gem "rdoc", ">= 2.5.10" gem "horo", ">= 1.0.2" +# for perf tests +gem "faker" +gem "rbench" +gem "addressable" + # AS gem "memcache-client", ">= 1.8.5" @@ -22,6 +27,7 @@ gem "text-format", "~> 1.0.0" platforms :mri_18 do gem "system_timer" gem "ruby-debug", ">= 0.10.3" + gem 'ruby-prof' end platforms :mri_19 do diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec index daf30e434a..6cdf10e255 100644 --- a/actionmailer/actionmailer.gemspec +++ b/actionmailer/actionmailer.gemspec @@ -20,5 +20,5 @@ Gem::Specification.new do |s| s.has_rdoc = true s.add_dependency('actionpack', version) - s.add_dependency('mail', '~> 2.2.5') + s.add_dependency('mail', '~> 2.2.6') end diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index b37bc02127..631a0f2945 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -148,6 +148,8 @@ module ActionController # # In this case, after saving our new entry to the database, the user is redirected to the <tt>show</tt> method which is then executed. # + # Learn more about <tt>redirect_to</tt> and what options you have in ActionController::Redirecting. + # # == Calling multiple redirects or renders # # An action may contain only a single render or a single redirect. Attempting to try to do either again will result in a DoubleRenderError: diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index b5f1d23ef0..10d7794b57 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -38,6 +38,9 @@ module ActionController # redirect_to :action=>'atom', :status => :moved_permanently # redirect_to post_url(@post), :status => 301 # redirect_to :action=>'atom', :status => 302 + # + # The status code can either be a standard {HTTP Status code}[http://www.iana.org/assignments/http-status-codes] as an + # integer, or a symbol representing the downcased, underscored and symbolized description. # # It is also possible to assign a flash message as part of the redirection. There are two special accessors for commonly used the flash names # +alert+ and +notice+ as well as a general purpose +flash+ bucket. @@ -48,8 +51,7 @@ module ActionController # redirect_to post_url(@post), :status => 301, :flash => { :updated_post_id => @post.id } # redirect_to { :action=>'atom' }, :alert => "Something serious happened" # - # When using <tt>redirect_to :back</tt>, if there is no referrer, - # RedirectBackError will be raised. You may specify some fallback + # When using <tt>redirect_to :back</tt>, if there is no referrer, RedirectBackError will be raised. You may specify some fallback # behavior for this case by rescuing RedirectBackError. def redirect_to(options = {}, response_status = {}) #:doc: raise ActionControllerError.new("Cannot redirect to nil!") if options.nil? diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 38f46fa120..2e39d0dbc2 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -1,7 +1,9 @@ module ActionDispatch module Http module URL - # Returns the complete \URL used for this request. + mattr_accessor :tld_length + + # Returns the complete URL used for this request. def url protocol + host_with_port + fullpath end @@ -85,13 +87,13 @@ module ActionDispatch # returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>, # such as 2 to catch <tt>["www"]</tt> instead of <tt>["www", "rubyonrails"]</tt> # in "www.rubyonrails.co.uk". - def subdomains(tld_length = 1) + def subdomains(tld_length = @@tld_length) return [] unless named_host?(host) parts = host.split('.') parts[0..-(tld_length+2)] end - def subdomain(tld_length = 1) + def subdomain(tld_length = @@tld_length) subdomains(tld_length).join('.') end @@ -102,4 +104,4 @@ module ActionDispatch end end end -end +end
\ No newline at end of file diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index ed066ad75e..c202fee990 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -8,5 +8,15 @@ module ActionDispatch config.action_dispatch.ip_spoofing_check = true config.action_dispatch.show_exceptions = true config.action_dispatch.best_standards_support = true + config.action_dispatch.tld_length = 1 + + # Prepare dispatcher callbacks and run 'prepare' callbacks + initializer "action_dispatch.prepare_dispatcher" do |app| + ActionDispatch::Callbacks.to_prepare { app.routes_reloader.execute_if_updated } + end + + initializer "action_dispatch.configure" do |app| + ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length + end end end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 3329a8b368..fdc40c8f2e 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -257,53 +257,53 @@ module ActionView end alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route - # Returns an html script tag for each of the +sources+ provided. You - # can pass in the filename (.js extension is optional) of javascript files - # that exist in your public/javascripts directory for inclusion into the + # Returns an HTML script tag for each of the +sources+ provided. You + # can pass in the filename (.js extension is optional) of JavaScript files + # that exist in your <tt>public/javascripts</tt> directory for inclusion into the # current page or you can pass the full path relative to your document - # root. To include the Prototype and Scriptaculous javascript libraries in + # root. To include the Prototype and Scriptaculous JavaScript libraries in # your application, pass <tt>:defaults</tt> as the source. When using - # <tt>:defaults</tt>, if an application.js file exists in your public - # javascripts directory, it will be included as well. You can modify the - # html attributes of the script tag by passing a hash as the last argument. + # <tt>:defaults</tt>, if an <tt>application.js</tt> file exists in + # <tt>public/javascripts</tt> it will be included as well. You can modify the + # HTML attributes of the script tag by passing a hash as the last argument. # # ==== Examples # javascript_include_tag "xmlhr" # => - # <script type="text/javascript" src="/javascripts/xmlhr.js"></script> + # <script type="text/javascript" src="/javascripts/xmlhr.js?1284139606"></script> # # javascript_include_tag "xmlhr.js" # => - # <script type="text/javascript" src="/javascripts/xmlhr.js"></script> + # <script type="text/javascript" src="/javascripts/xmlhr.js?1284139606"></script> # # javascript_include_tag "common.javascript", "/elsewhere/cools" # => - # <script type="text/javascript" src="/javascripts/common.javascript"></script> - # <script type="text/javascript" src="/elsewhere/cools.js"></script> + # <script type="text/javascript" src="/javascripts/common.javascript?1284139606"></script> + # <script type="text/javascript" src="/elsewhere/cools.js?1423139606"></script> # # javascript_include_tag "http://www.railsapplication.com/xmlhr" # => - # <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js"></script> + # <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js?1284139606"></script> # # javascript_include_tag "http://www.railsapplication.com/xmlhr.js" # => - # <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js"></script> + # <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js?1284139606"></script> # # javascript_include_tag :defaults # => - # <script type="text/javascript" src="/javascripts/prototype.js"></script> - # <script type="text/javascript" src="/javascripts/effects.js"></script> + # <script type="text/javascript" src="/javascripts/prototype.js?1284139606"></script> + # <script type="text/javascript" src="/javascripts/effects.js?1284139606"></script> # ... - # <script type="text/javascript" src="/javascripts/application.js"></script> + # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script> # # * = The application.js file is only referenced if it exists # # Though it's not really recommended practice, if you need to extend the default JavaScript set for any reason # (e.g., you're going to be using a certain .js file in every action), then take a look at the register_javascript_include_default method. # - # You can also include all javascripts in the javascripts directory using <tt>:all</tt> as the source: + # You can also include all javascripts in the +javascripts+ directory using <tt>:all</tt> as the source: # # javascript_include_tag :all # => - # <script type="text/javascript" src="/javascripts/prototype.js"></script> - # <script type="text/javascript" src="/javascripts/effects.js"></script> + # <script type="text/javascript" src="/javascripts/prototype.js?1284139606"></script> + # <script type="text/javascript" src="/javascripts/effects.js?1284139606"></script> # ... - # <script type="text/javascript" src="/javascripts/application.js"></script> - # <script type="text/javascript" src="/javascripts/shop.js"></script> - # <script type="text/javascript" src="/javascripts/checkout.js"></script> + # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script> + # <script type="text/javascript" src="/javascripts/shop.js?1284139606"></script> + # <script type="text/javascript" src="/javascripts/checkout.js?1284139606"></script> # # Note that the default javascript files will be included first. So Prototype and Scriptaculous are available to # all subsequently included files. @@ -321,23 +321,23 @@ module ActionView # # ==== Examples # javascript_include_tag :all, :cache => true # when config.perform_caching is false => - # <script type="text/javascript" src="/javascripts/prototype.js"></script> - # <script type="text/javascript" src="/javascripts/effects.js"></script> + # <script type="text/javascript" src="/javascripts/prototype.js?1284139606"></script> + # <script type="text/javascript" src="/javascripts/effects.js?1284139606"></script> # ... - # <script type="text/javascript" src="/javascripts/application.js"></script> - # <script type="text/javascript" src="/javascripts/shop.js"></script> - # <script type="text/javascript" src="/javascripts/checkout.js"></script> + # <script type="text/javascript" src="/javascripts/application.js?1284139606"></script> + # <script type="text/javascript" src="/javascripts/shop.js?1284139606"></script> + # <script type="text/javascript" src="/javascripts/checkout.js?1284139606"></script> # # javascript_include_tag :all, :cache => true # when config.perform_caching is true => - # <script type="text/javascript" src="/javascripts/all.js"></script> + # <script type="text/javascript" src="/javascripts/all.js?1344139789"></script> # # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when config.perform_caching is false => - # <script type="text/javascript" src="/javascripts/prototype.js"></script> - # <script type="text/javascript" src="/javascripts/cart.js"></script> - # <script type="text/javascript" src="/javascripts/checkout.js"></script> + # <script type="text/javascript" src="/javascripts/prototype.js?1284139606"></script> + # <script type="text/javascript" src="/javascripts/cart.js?1289139157"></script> + # <script type="text/javascript" src="/javascripts/checkout.js?1299139816"></script> # # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when config.perform_caching is true => - # <script type="text/javascript" src="/javascripts/shop.js"></script> + # <script type="text/javascript" src="/javascripts/shop.js?1299139816"></script> # # The <tt>:recursive</tt> option is also available for caching: # diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 43dbedc448..6d70c63de9 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -293,31 +293,32 @@ module ActionView # # If you don't need to attach a form to a model instance, then check out # FormTagHelper#form_tag. - def form_for(record_or_name_or_array, *args, &proc) + def form_for(record, record_object = nil, options = nil, &proc) raise ArgumentError, "Missing block" unless block_given? - options = args.extract_options! + options, record_object = record_object, nil if record_object.is_a?(Hash) + options ||= {} - case record_or_name_or_array + case record when String, Symbol - ActiveSupport::Deprecation.warn("Using form_for(:name, @resource) is deprecated. Please use form_for(@resource, :as => :name) instead.", caller) unless args.empty? - object_name = record_or_name_or_array + ActiveSupport::Deprecation.warn("Using form_for(:name, @resource) is deprecated. Please use form_for(@resource, :as => :name) instead.", caller) if record_object + object_name = record + object = record_object when Array - object = record_or_name_or_array.last - object_name = options[:as] || ActiveModel::Naming.param_key(object) - apply_form_for_options!(record_or_name_or_array, options) - args.unshift object + object = record.last + object_name = options[:as] || ActiveModel::Naming.singular(object) + apply_form_for_options!(record, options) else - object = record_or_name_or_array - object_name = options[:as] || ActiveModel::Naming.param_key(object) + object = record + object_name = options[:as] || ActiveModel::Naming.singular(object) apply_form_for_options!([object], options) - args.unshift object end - (options[:html] ||= {})[:remote] = true if options.delete(:remote) + options[:html] ||= {} + options[:html][:remote] = options.delete(:remote) - output = form_tag(options.delete(:url) || {}, options.delete(:html) || {}) - output << fields_for(object_name, *(args << options), &proc) + output = form_tag(options.delete(:url) || {}, options.delete(:html) || {}) + output << fields_for(object_name, object, options, &proc) output.safe_concat('</form>') end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 546c4cb253..a8b8f9377b 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -116,6 +116,9 @@ class RequestTest < ActiveSupport::TestCase request = stub_request 'HTTP_HOST' => "dev.www.rubyonrails.co.uk" assert_equal %w( dev www ), request.subdomains(2) + request = stub_request 'HTTP_HOST' => "dev.www.rubyonrails.co.uk", :tld_length => 2 + assert_equal %w( dev www ), request.subdomains + request = stub_request 'HTTP_HOST' => "foobar.foobar.com" assert_equal %w( foobar ), request.subdomains @@ -472,7 +475,9 @@ protected def stub_request(env = {}) ip_spoofing_check = env.key?(:ip_spoofing_check) ? env.delete(:ip_spoofing_check) : true ip_app = ActionDispatch::RemoteIp.new(Proc.new { }, ip_spoofing_check, @trusted_proxies) + tld_length = env.key?(:tld_length) ? env.delete(:tld_length) : 1 ip_app.call(env) + ActionDispatch::Http::URL.tld_length = tld_length ActionDispatch::Request.new(env) end diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index b8126fb67e..44e3e64a9e 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -1,5 +1,5 @@ require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/class/inheritable_attributes' +require 'active_support/core_ext/class/attribute' module ActiveModel class MissingAttributeError < NoMethodError @@ -56,6 +56,11 @@ module ActiveModel module AttributeMethods extend ActiveSupport::Concern + included do + class_attribute :attribute_method_matchers, :instance_writer => false + self.attribute_method_matchers = [] + end + module ClassMethods # Defines an "attribute" method (like +inheritance_column+ or +table_name+). # A new (class) method will be created with the given name. If a value is @@ -143,7 +148,7 @@ module ActiveModel # person.clear_name # person.name # => nil def attribute_method_prefix(*prefixes) - attribute_method_matchers.concat(prefixes.map { |prefix| AttributeMethodMatcher.new :prefix => prefix }) + self.attribute_method_matchers += prefixes.map { |prefix| AttributeMethodMatcher.new :prefix => prefix } undefine_attribute_methods end @@ -180,7 +185,7 @@ module ActiveModel # person.name # => "Bob" # person.name_short? # => true def attribute_method_suffix(*suffixes) - attribute_method_matchers.concat(suffixes.map { |suffix| AttributeMethodMatcher.new :suffix => suffix }) + self.attribute_method_matchers += suffixes.map { |suffix| AttributeMethodMatcher.new :suffix => suffix } undefine_attribute_methods end @@ -218,7 +223,7 @@ module ActiveModel # person.reset_name_to_default! # person.name # => 'Gemma' def attribute_method_affix(*affixes) - attribute_method_matchers.concat(affixes.map { |affix| AttributeMethodMatcher.new :prefix => affix[:prefix], :suffix => affix[:suffix] }) + self.attribute_method_matchers += affixes.map { |affix| AttributeMethodMatcher.new :prefix => affix[:prefix], :suffix => affix[:suffix] } undefine_attribute_methods end @@ -312,7 +317,7 @@ module ActiveModel private class AttributeMethodMatcher - attr_reader :prefix, :suffix + attr_reader :prefix, :suffix, :method_missing_target AttributeMethodMatch = Struct.new(:target, :attr_name) @@ -320,28 +325,22 @@ module ActiveModel options.symbolize_keys! @prefix, @suffix = options[:prefix] || '', options[:suffix] || '' @regex = /^(#{Regexp.escape(@prefix)})(.+?)(#{Regexp.escape(@suffix)})$/ + @method_missing_target = :"#{@prefix}attribute#{@suffix}" + @method_name = "#{prefix}%s#{suffix}" end def match(method_name) - if matchdata = @regex.match(method_name) - AttributeMethodMatch.new(method_missing_target, matchdata[2]) + if @regex =~ method_name + AttributeMethodMatch.new(method_missing_target, $2) else nil end end def method_name(attr_name) - "#{prefix}#{attr_name}#{suffix}" - end - - def method_missing_target - :"#{prefix}attribute#{suffix}" + @method_name % attr_name end end - - def attribute_method_matchers #:nodoc: - read_inheritable_attribute(:attribute_method_matchers) || write_inheritable_attribute(:attribute_method_matchers, []) - end end # Allows access to the object attributes, which are held in the @@ -390,7 +389,7 @@ module ActiveModel # Returns a struct representing the matching attribute method. # The struct's attributes are prefix, base and suffix. def match_attribute_method?(method_name) - self.class.send(:attribute_method_matchers).each do |method| + self.class.attribute_method_matchers.each do |method| if (match = method.match(method_name)) && attribute_method?(match.attr_name) return match end @@ -401,7 +400,7 @@ module ActiveModel # prevent method_missing from calling private methods with #send def guard_private_attribute_method!(method_name, args) if self.class.private_method_defined?(method_name) - raise NoMethodError.new("Attempt to call private method", method_name, args) + raise NoMethodError.new("Attempt to call private method `#{method_name}'", method_name, args) end end diff --git a/activerecord/examples/performance.rb b/activerecord/examples/performance.rb index a985cfcb66..ccd60c6c69 100644 --- a/activerecord/examples/performance.rb +++ b/activerecord/examples/performance.rb @@ -25,7 +25,7 @@ conn[:socket] = Pathname.glob(%w[ /tmp/mysql.sock /var/mysql/mysql.sock /var/run/mysqld/mysqld.sock -]).find { |path| path.socket? } +]).find { |path| path.socket? }.to_s ActiveRecord::Base.establish_connection(conn) @@ -155,6 +155,23 @@ RBench.run(TIMES) do ar { Exhibit.transaction { Exhibit.new } } end + report 'Model.find(id)' do + id = Exhibit.first.id + ar { Exhibit.find(id) } + end + + report 'Model.find_by_sql' do + ar { Exhibit.find_by_sql("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}").first } + end + + report 'Model.log', (TIMES * 10) do + ar { Exhibit.connection.send(:log, "hello", "world") {} } + end + + report 'AR.execute(query)', (TIMES / 2) do + ar { ActiveRecord::Base.connection.execute("Select * from exhibits where id = #{(rand * 1000 + 1).to_i}") } + end + summary 'Total' end diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 56e18eced0..7297af9f79 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -48,13 +48,13 @@ module ActiveRecord end def respond_to?(*args) - self.class.define_attribute_methods + self.class.define_attribute_methods unless self.class.attribute_methods_generated? super end protected def attribute_method?(attr_name) - attr_name == 'id' || attributes.include?(attr_name) + attr_name == 'id' || @attributes.include?(attr_name) end end end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 3da3d9d8cc..01699746d8 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -26,8 +26,7 @@ module ActiveRecord # Returns the attributes which are cached. By default time related columns # with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached. def cached_attributes - @cached_attributes ||= - columns.select{|c| attribute_types_cached_by_default.include?(c.type)}.map{|col| col.name}.to_set + @cached_attributes ||= columns.select { |c| cacheable_column?(c) }.map { |col| col.name }.to_set end # Returns +true+ if the provided attribute is being cached. @@ -37,7 +36,7 @@ module ActiveRecord protected def define_method_attribute(attr_name) - if self.serialized_attributes[attr_name] + if serialized_attributes.include?(attr_name) define_read_method_for_serialized_attribute(attr_name) else define_read_method(attr_name.to_sym, attr_name, columns_hash[attr_name]) @@ -49,9 +48,14 @@ module ActiveRecord end private + def cacheable_column?(column) + serialized_attributes.include?(column.name) || attribute_types_cached_by_default.include?(column.type) + end + # Define read method for serialized attribute. def define_read_method_for_serialized_attribute(attr_name) - generated_attribute_methods.module_eval("def #{attr_name}; unserialize_attribute('#{attr_name}'); end", __FILE__, __LINE__) + access_code = "@attributes_cache['#{attr_name}'] ||= unserialize_attribute('#{attr_name}')" + generated_attribute_methods.module_eval("def #{attr_name}; #{access_code}; end", __FILE__, __LINE__) end # Define an attribute reader method. Cope with nil column. @@ -66,13 +70,19 @@ module ActiveRecord if cache_attribute?(attr_name) access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})" end - generated_attribute_methods.module_eval("def #{symbol}; #{access_code}; end", __FILE__, __LINE__) + generated_attribute_methods.module_eval("def _#{symbol}; #{access_code}; end; alias #{symbol} _#{symbol}", __FILE__, __LINE__) end end # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example, # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). def read_attribute(attr_name) + send "_#{attr_name}" + rescue NoMethodError + _read_attribute attr_name + end + + def _read_attribute(attr_name) attr_name = attr_name.to_s attr_name = self.class.primary_key if attr_name == 'id' if !(value = @attributes[attr_name]).nil? @@ -85,14 +95,12 @@ module ActiveRecord else value end - else - nil end end # Returns true if the attribute is of a text column and marked for serialization. def unserializable_attribute?(attr_name, column) - column.text? && self.class.serialized_attributes[attr_name] + column.text? && self.class.serialized_attributes.include?(attr_name) end # Returns the unserialized object of the attribute. diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index a258b3f431..d640b26b74 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -19,12 +19,13 @@ module ActiveRecord def define_method_attribute(attr_name) if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name]) method_body, line = <<-EOV, __LINE__ + 1 - def #{attr_name}(reload = false) + def _#{attr_name}(reload = false) cached = @attributes_cache['#{attr_name}'] return cached if cached && !reload - time = read_attribute('#{attr_name}') + time = _read_attribute('#{attr_name}') @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time end + alias #{attr_name} _#{attr_name} EOV generated_attribute_methods.module_eval(method_body, __FILE__, line) else diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index ef4834811c..5da4eb169b 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -415,6 +415,11 @@ module ActiveRecord #:nodoc: class_inheritable_accessor :default_scoping, :instance_writer => false self.default_scoping = [] + # Returns a hash of all the attributes that have been specified for serialization as + # keys and their class restriction as values. + class_attribute :serialized_attributes + self.serialized_attributes = {} + class << self # Class methods 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 @@ -526,12 +531,6 @@ module ActiveRecord #:nodoc: serialized_attributes[attr_name.to_s] = class_name end - # Returns a hash of all the attributes that have been specified for serialization as - # keys and their class restriction as values. - def serialized_attributes - read_inheritable_attribute(:attr_serialized) or write_inheritable_attribute(:attr_serialized, {}) - end - # Guesses the table name (in forced lower-case) based on the name of the class in the # inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy # looks like: Reply < Message < ActiveRecord::Base, then Message is used @@ -664,7 +663,7 @@ module ActiveRecord #:nodoc: # Returns a hash of column objects for the table associated with this class. def columns_hash - @columns_hash ||= columns.inject({}) { |hash, column| hash[column.name] = column; hash } + @columns_hash ||= Hash[columns.map { |column| [column.name, column] }] end # Returns an array of column names as strings. @@ -1080,9 +1079,9 @@ module ActiveRecord #:nodoc: if method_scoping.is_a?(Hash) # Dup first and second level of hash (method and params). - method_scoping = method_scoping.inject({}) do |hash, (method, params)| - hash[method] = (params == true) ? params : params.dup - hash + method_scoping = method_scoping.dup + method_scoping.each do |method, params| + method_scoping[method] = params.dup unless params == true end method_scoping.assert_valid_keys([ :find, :create ]) @@ -1793,10 +1792,7 @@ MSG end def quote_columns(quoter, hash) - hash.inject({}) do |quoted, (name, value)| - quoted[quoter.quote_column_name(name)] = value - quoted - end + Hash[hash.map { |name, value| [quoter.quote_column_name(name), value] }] end def quoted_comma_pair_list(quoter, hash) diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index d7494ebb5a..5584397439 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -50,7 +50,7 @@ module ActiveRecord def find_in_batches(options = {}) relation = self - if orders.present? || taken.present? + unless arel.orders.blank? && arel.taken.blank? ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size") end diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index a80ac40a2c..398ab75b69 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -175,12 +175,11 @@ module ActiveRecord end distinct = options[:distinct] || distinct - column_name = :all if column_name.blank? && operation == "count" if @group_values.any? - return execute_grouped_calculation(operation, column_name) + execute_grouped_calculation(operation, column_name) else - return execute_simple_calculation(operation, column_name, distinct) + execute_simple_calculation(operation, column_name, distinct) end end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 4ffb552690..ede1c8821e 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -230,7 +230,7 @@ module ActiveRecord end def find_by_attributes(match, attributes, *args) - conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h} + conditions = Hash[attributes.map {|a| [a, args[attributes.index(a)]]}] result = where(conditions).send(match.finder) if match.bang? && result.blank? diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 5cea2328e8..0d1307d87e 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -24,7 +24,9 @@ module ActiveRecord case value when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::Relation - values = value.to_a + values = value.to_a.map { |x| + x.respond_to?(:quoted_id) ? x.quoted_id : x + } attribute.in(values) when Range, Arel::Relation attribute.in(value) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index cee240e6a1..de57725af2 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -12,20 +12,22 @@ module ActiveRecord def includes(*args) args.reject! {|a| a.blank? } + return clone if args.empty? + relation = clone - relation.includes_values = (relation.includes_values + args).flatten.uniq if args.present? + relation.includes_values = (relation.includes_values + args).flatten.uniq relation end def eager_load(*args) relation = clone - relation.eager_load_values += args if args.present? + relation.eager_load_values += args unless args.blank? relation end def preload(*args) relation = clone - relation.preload_values += args if args.present? + relation.preload_values += args unless args.blank? relation end @@ -41,19 +43,19 @@ module ActiveRecord def group(*args) relation = clone - relation.group_values += args.flatten if args.present? + relation.group_values += args.flatten unless args.blank? relation end def order(*args) relation = clone - relation.order_values += args.flatten if args.present? + relation.order_values += args.flatten unless args.blank? relation end def reorder(*args) relation = clone - if args.present? + unless args.blank? relation.order_values = args relation.reorder_flag = true end @@ -63,31 +65,21 @@ module ActiveRecord def joins(*args) relation = clone - if args.present? - args.flatten! - relation.joins_values += args if args.present? - end + args.flatten! + relation.joins_values += args unless args.blank? relation end def where(opts, *rest) relation = clone - - if opts.present? && value = build_where(opts, rest) - relation.where_values += Array.wrap(value) - end - + relation.where_values += build_where(opts, rest) unless opts.blank? relation end def having(*args) relation = clone - - if args.present? && value = build_where(*args) - relation.having_values += Array.wrap(value) - end - + relation.having_values += build_where(*args) unless args.blank? relation end @@ -138,7 +130,7 @@ module ActiveRecord modules << Module.new(&block) if block_given? relation = clone - relation.send(:apply_modules, *modules) + relation.send(:apply_modules, modules.flatten) relation end @@ -196,14 +188,14 @@ module ActiveRecord end end - arel = arel.having(*@having_values.uniq.select{|h| h.present?}) unless @having_values.empty? + arel = arel.having(*@having_values.uniq.reject{|h| h.blank?}) unless @having_values.empty? arel = arel.take(@limit_value) if @limit_value arel = arel.skip(@offset_value) if @offset_value - arel = arel.group(*@group_values.uniq.select{|g| g.present?}) unless @group_values.empty? + arel = arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty? - arel = arel.order(*@order_values.uniq.select{|o| o.present?}) unless @order_values.empty? + arel = arel.order(*@order_values.uniq.reject{|o| o.blank?}) unless @order_values.empty? arel = build_select(arel, @select_values.uniq) @@ -216,12 +208,12 @@ module ActiveRecord def build_where(opts, other = []) case opts when String, Array - @klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other)) + [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))] when Hash attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts) PredicateBuilder.new(table.engine).build_from_hash(attributes, table) else - opts + [opts] end end @@ -284,9 +276,10 @@ module ActiveRecord end def apply_modules(modules) - values = Array.wrap(modules) - @extensions += values if values.present? - values.each {|extension| extend(extension) } + unless modules.empty? + @extensions += modules + modules.each {|extension| extend(extension) } + end end def reverse_sql_order(order_query) diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index a6cf59fbf1..9ecdb99bee 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -5,6 +5,7 @@ module ActiveRecord def merge(r) merged_relation = clone return merged_relation unless r + return to_a & r if r.is_a?(Array) Relation::ASSOCIATION_METHODS.each do |method| value = r.send(:"#{method}_values") @@ -64,12 +65,12 @@ module ActiveRecord def except(*skips) result = self.class.new(@klass, table) - (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).each do |method| - result.send(:"#{method}_values=", send(:"#{method}_values")) unless skips.include?(method) + ((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) - skips).each do |method| + result.send(:"#{method}_values=", send(:"#{method}_values")) end - Relation::SINGLE_VALUE_METHODS.each do |method| - result.send(:"#{method}_value=", send(:"#{method}_value")) unless skips.include?(method) + (Relation::SINGLE_VALUE_METHODS - skips).each do |method| + result.send(:"#{method}_value=", send(:"#{method}_value")) end result @@ -78,14 +79,12 @@ module ActiveRecord def only(*onlies) result = self.class.new(@klass, table) - onlies.each do |only| - if (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).include?(only) - result.send(:"#{only}_values=", send(:"#{only}_values")) - elsif Relation::SINGLE_VALUE_METHODS.include?(only) - result.send(:"#{only}_value=", send(:"#{only}_value")) - else - raise "Invalid argument : #{only}" - end + ((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) & onlies).each do |method| + result.send(:"#{method}_values=", send(:"#{method}_values")) + end + + (Relation::SINGLE_VALUE_METHODS & onlies).each do |method| + result.send(:"#{method}_value=", send(:"#{method}_value")) end result @@ -99,22 +98,16 @@ module ActiveRecord return relation unless options options.assert_valid_keys(VALID_FIND_OPTIONS) + finders = options.dup + finders.delete_if { |key, value| value.nil? } - [:joins, :select, :group, :having, :limit, :offset, :from, :lock].each do |finder| - if value = options[finder] - relation = relation.send(finder, value) - end - end - - relation = relation.readonly(options[:readonly]) if options.key? :readonly - - [:group, :order].each do |finder| - relation.send("#{finder}_values=", relation.send("#{finder}_values") + Array.wrap(options[finder])) if options.has_key?(finder) + ([:joins, :select, :group, :order, :having, :limit, :offset, :from, :lock, :readonly] & finders.keys).each do |finder| + relation = relation.send(finder, finders[finder]) end - relation = relation.where(options[:conditions]) if options.has_key?(:conditions) - relation = relation.includes(options[:include]) if options.has_key?(:include) - relation = relation.extending(options[:extend]) if options.has_key?(:extend) + relation = relation.where(finders[:conditions]) if options.has_key?(:conditions) + relation = relation.includes(finders[:include]) if options.has_key?(:include) + relation = relation.extending(finders[:extend]) if options.has_key?(:extend) relation end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 9b3cc47c79..1750bf004a 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -419,12 +419,8 @@ class AttributeMethodsTest < ActiveRecord::TestCase Topic.instance_variable_set "@cached_attributes", nil end - def test_time_related_columns_are_actually_cached - column_types = %w(datetime timestamp time date).map(&:to_sym) - column_names = Topic.columns.select{|c| column_types.include?(c.type) }.map(&:name) - - assert_equal column_names.sort, Topic.cached_attributes.sort - assert_equal time_related_columns_on_topic.sort, Topic.cached_attributes.sort + def test_cacheable_columns_are_actually_cached + assert_equal cached_columns.sort, Topic.cached_attributes.sort end def test_accessing_cached_attributes_caches_the_converted_values_and_nothing_else @@ -435,8 +431,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert cache.empty? all_columns = Topic.columns.map(&:name) - cached_columns = time_related_columns_on_topic - uncached_columns = all_columns - cached_columns + uncached_columns = all_columns - cached_columns all_columns.each do |attr_name| attribute_gets_cached = Topic.cache_attribute?(attr_name) @@ -546,7 +541,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase topic = @target.new(:title => "The pros and cons of programming naked.") assert !topic.respond_to?(:title) exception = assert_raise(NoMethodError) { topic.title } - assert_equal "Attempt to call private method", exception.message + assert_match %r(^Attempt to call private method), exception.message assert_equal "I'm private", topic.send(:title) end @@ -556,7 +551,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase topic = @target.new assert !topic.respond_to?(:title=) exception = assert_raise(NoMethodError) { topic.title = "Pants"} - assert_equal "Attempt to call private method", exception.message + assert_match %r(^Attempt to call private method), exception.message topic.send(:title=, "Very large pants") end @@ -566,7 +561,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase topic = @target.new(:title => "Isaac Newton's pants") assert !topic.respond_to?(:title?) exception = assert_raise(NoMethodError) { topic.title? } - assert_equal "Attempt to call private method", exception.message + assert_match %r(^Attempt to call private method), exception.message assert topic.send(:title?) end @@ -594,8 +589,16 @@ class AttributeMethodsTest < ActiveRecord::TestCase private + def cached_columns + @cached_columns ||= (time_related_columns_on_topic + serialized_columns_on_topic).map(&:name) + end + def time_related_columns_on_topic - Topic.columns.select{|c| [:time, :date, :datetime, :timestamp].include?(c.type)}.map(&:name) + Topic.columns.select { |c| [:time, :date, :datetime, :timestamp].include?(c.type) } + end + + def serialized_columns_on_topic + Topic.columns.select { |c| Topic.serialized_attributes.include?(c.name) } end def in_time_zone(zone) diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index 312628a3e3..ffe16ffdfa 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -400,7 +400,7 @@ class NestedScopingTest < ActiveRecord::TestCase Developer.send(:with_scope, :find => { :conditions => "salary < 100000" }) do Developer.send(:with_scope, :find => { :offset => 1, :order => 'id asc' }) do # Oracle adapter does not generated space after asc therefore trailing space removed from regex - assert_sql(/ORDER BY id asc/) do + assert_sql(/ORDER BY\s+id asc/) do assert_equal(poor_jamis, Developer.find(:first, :order => 'id asc')) end end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index b7112d0e37..649fe79fd3 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -647,6 +647,17 @@ class RelationTest < ActiveRecord::TestCase assert_equal Post.all, all_posts.all end + def test_only + relation = Post.where(:author_id => 1).order('id ASC').limit(1) + assert_equal [posts(:welcome)], relation.all + + author_posts = relation.only(:where) + assert_equal Post.where(:author_id => 1).all, author_posts.all + + all_posts = relation.only(:limit) + assert_equal Post.limit(1).all.first, all_posts.first + end + def test_anonymous_extension relation = Post.where(:author_id => 1).order('id ASC').extending do def author @@ -688,5 +699,11 @@ class RelationTest < ActiveRecord::TestCase assert_equal 'zyke', car.name end + def test_intersection_with_array + relation = Author.where(:name => "David") + rails_author = relation.first + assert_equal [rails_author], [rails_author] & relation + assert_equal [rails_author], relation & [rails_author] + end end diff --git a/activerecord/test/models/subject.rb b/activerecord/test/models/subject.rb index 1b9d8107f8..d4b8b91de8 100644 --- a/activerecord/test/models/subject.rb +++ b/activerecord/test/models/subject.rb @@ -1,12 +1,16 @@ # used for OracleSynonymTest, see test/synonym_test_oracle.rb # class Subject < ActiveRecord::Base + + # added initialization of author_email_address in the same way as in Topic class + # as otherwise synonym test was failing + after_initialize :set_email_address + protected - # added initialization of author_email_address in the same way as in Topic class - # as otherwise synonym test was failing - def after_initialize + def set_email_address if self.new_record? self.author_email_address = 'test@test.com' end end + end diff --git a/railties/guides/source/plugins.textile b/railties/guides/source/plugins.textile index 2300786791..677a8a3667 100644 --- a/railties/guides/source/plugins.textile +++ b/railties/guides/source/plugins.textile @@ -33,20 +33,20 @@ h3. Setup h4. Create the Basic Application -The examples in this guide require that you have a working rails application. To create a simple rails app execute: +The examples in this guide require that you have a working rails application. To create a simple one execute: <shell> gem install rails -rails yaffle_guide +rails new yaffle_guide cd yaffle_guide rails generate scaffold bird name:string rake db:migrate rails server </shell> -Then navigate to http://localhost:3000/birds. Make sure you have a functioning rails app before continuing. +Then navigate to http://localhost:3000/birds. Make sure you have a functioning rails application before continuing. -NOTE: The aforementioned instructions will work for sqlite3. For more detailed instructions on how to create a rails app for other databases see the API docs. +NOTE: The aforementioned instructions will work for sqlite3. For more detailed instructions on how to create a rails application for other databases see the API docs. h4. Generate the Plugin Skeleton @@ -277,7 +277,7 @@ Now you are ready to test-drive your plugin! h3. Extending Core Classes -This section will explain how to add a method to String that will be available anywhere in your rails app. +This section will explain how to add a method to String that will be available anywhere in your rails application. In this example you will add a method to String named +to_squawk+. To begin, create a new test file with a few assertions: @@ -622,7 +622,7 @@ create_table :woodpeckers, :force => true do |t| end </ruby> -Now your test should be passing, and you should be able to use the Woodpecker model from within your rails app, and any changes made to it are reflected immediately when running in development mode. +Now your test should be passing, and you should be able to use the Woodpecker model from within your rails application, and any changes made to it are reflected immediately when running in development mode. h3. Controllers @@ -734,7 +734,7 @@ h3. Routes In a standard 'routes.rb' file you use routes like 'map.connect' or 'map.resources'. You can add your own custom routes from a plugin. This section will describe how to add a custom method called that can be called with 'map.yaffles'. -Testing routes from plugins is slightly different from testing routes in a standard rails app. To begin, add a test like this: +Testing routes from plugins is slightly different from testing routes in a standard rails application. To begin, add a test like this: * *vendor/plugins/yaffle/test/routing_test.rb* @@ -1385,7 +1385,7 @@ rake gem sudo gem install pkg/yaffle-0.0.1.gem </shell> -To test this, create a new rails app, add +config.gem "yaffle"+ to +config/environment.rb+ and all of your plugin's functionality will be available to you. +To test this, create a new rails application, add +config.gem "yaffle"+ to +config/environment.rb+ and all of your plugin's functionality will be available to you. h3. RDoc Documentation diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index da8ae17518..6970ea7b7a 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -65,6 +65,13 @@ module ApplicationTests assert_equal ["notify"], Foo.action_methods end + # AD + test "action_dispatch extensions are applied to ActionDispatch" do + add_to_config "config.action_dispatch.tld_length = 2" + require "#{app_path}/config/environment" + assert_equal 2, ActionDispatch::Http::URL.tld_length + end + # AS test "if there's no config.active_support.bare, all of ActiveSupport is required" do use_frameworks [] |