diff options
22 files changed, 310 insertions, 347 deletions
diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb index efc35a7e56..725d8fb8fc 100644 --- a/actionpack/lib/abstract_controller.rb +++ b/actionpack/lib/abstract_controller.rb @@ -15,5 +15,6 @@ module AbstractController autoload :LocalizedCache autoload :Logger autoload :Rendering + autoload :Translation autoload :UrlFor end diff --git a/actionpack/lib/abstract_controller/localized_cache.rb b/actionpack/lib/abstract_controller/localized_cache.rb index bf648af60a..5e3efa002c 100644 --- a/actionpack/lib/abstract_controller/localized_cache.rb +++ b/actionpack/lib/abstract_controller/localized_cache.rb @@ -34,7 +34,7 @@ module AbstractController end end - def render(options) + def render(*args) Thread.current[:format_locale_key] = HashKey.get(self.class, formats, I18n.locale) super end diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 0dab4a3cc0..826e82c8c6 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -40,12 +40,13 @@ module AbstractController # Mostly abstracts the fact that calling render twice is a DoubleRenderError. # Delegates render_to_body and sticks the result in self.response_body. - def render(*args) + def render(*args, &block) if response_body - raise AbstractController::DoubleRenderError, "Can only render or redirect once per action" + raise AbstractController::DoubleRenderError end - self.response_body = render_to_body(*args) + options = _normalize_options(*args, &block) + self.response_body = render_to_body(options) end # Raw rendering of a template to a Rack-compatible body. @@ -69,7 +70,8 @@ module AbstractController # render_to_body into a String. # # :api: plugin - def render_to_string(options = {}) + def render_to_string(*args) + options = _normalize_options(*args) AbstractController::Rendering.body_to_s(render_to_body(options)) end @@ -96,6 +98,20 @@ module AbstractController _view_paths end + # Normalize options, by converting render "foo" to render :template => "foo" + # and render "/foo" to render :file => "/foo". + def _normalize_options(action=nil, options={}) + case action + when Hash + options, action = action, nil + when String + key = (action.index("/") == 0 ? :file : :template) + options.merge!(key => action) + end + + options + end + # Return a string representation of a Rack-compatible response body. def self.body_to_s(body) if body.respond_to?(:to_str) diff --git a/actionpack/lib/action_controller/translation.rb b/actionpack/lib/abstract_controller/translation.rb index 65e9eddb0a..6d68cf4944 100644 --- a/actionpack/lib/action_controller/translation.rb +++ b/actionpack/lib/abstract_controller/translation.rb @@ -1,4 +1,4 @@ -module ActionController +module AbstractController module Translation def translate(*args) I18n.translate(*args) diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 8bc2cc79d2..fa4a253ec1 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -8,7 +8,6 @@ module ActionController autoload :Base autoload :Caching autoload :PolymorphicRoutes - autoload :Translation autoload :Metal autoload :Middleware diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index f86a61d791..21a811c004 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -4,6 +4,7 @@ module ActionController include AbstractController::Callbacks include AbstractController::Layouts + include AbstractController::Translation include ActionController::Helpers helper :all # By default, all helpers should be included @@ -33,7 +34,6 @@ module ActionController include ActionController::Streaming include ActionController::HttpAuthentication::Basic::ControllerMethods include ActionController::HttpAuthentication::Digest::ControllerMethods - include ActionController::Translation # Add instrumentations hooks at the bottom, to ensure they instrument # all the methods properly. @@ -74,17 +74,14 @@ module ActionController @subclasses ||= [] end - def _normalize_options(action = nil, options = {}, &blk) - if action.is_a?(Hash) - options, action = action, nil - elsif action.is_a?(String) || action.is_a?(Symbol) - key = case action = action.to_s - when %r{^/} then :file - when %r{/} then :template - else :action - end - options.merge! key => action - elsif action + def _normalize_options(action=nil, options={}, &blk) + case action + when NilClass + when Hash, String + options = super + when Symbol + options.merge! :action => action + else options.merge! :partial => action end @@ -99,15 +96,5 @@ module ActionController options[:update] = blk if block_given? options end - - def render(action = nil, options = {}, &blk) - options = _normalize_options(action, options, &blk) - super(options) - end - - def render_to_string(action = nil, options = {}, &blk) - options = _normalize_options(action, options, &blk) - super(options) - end end end diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 1819c0f886..57627a6f0b 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -60,6 +60,7 @@ module ActionController # :api: private def dispatch(name, env) @_env = env + @_env['action_controller.instance'] = self process(name) to_a end diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index 7b2b037c67..19c962bafa 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -32,18 +32,12 @@ module ActionController end end - def render(*args, &block) - if logger - render_output = nil - - self.view_runtime = cleanup_view_runtime do - Benchmark.ms { render_output = super } - end - - render_output - else - super + def render(*args) + render_output = nil + self.view_runtime = cleanup_view_runtime do + Benchmark.ms { render_output = super } end + render_output end def send_file(path, options={}) diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index 74e50bb032..475ed54167 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -12,9 +12,10 @@ module ActionController super end - def render(options) - super - self.content_type ||= options[:_template].mime_type.to_s + def render(*args) + args << {} unless args.last.is_a?(Hash) + super(*args) + self.content_type ||= args.last[:_template].mime_type.to_s response_body end diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 97546d5f93..68ba3637bf 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -29,9 +29,31 @@ module ActionDispatch def path_parameters @env["action_dispatch.request.path_parameters"] ||= {} end - - private + def filter_parameters + # TODO: Remove dependency on controller + if controller = @env['action_controller.instance'] + controller.send(:filter_parameters, params) + else + params + end + end + + def filter_env + if controller = @env['action_controller.instance'] + @env.map do |key, value| + if (key =~ /RAW_POST_DATA/i) + '[FILTERED]' + else + controller.send(:filter_parameters, {key => value}).values[0] + end + end + else + env + end + end + + private # Convert nested Hashs to HashWithIndifferentAccess def normalize_parameters(value) case value diff --git a/actionpack/lib/action_dispatch/middleware/notifications.rb b/actionpack/lib/action_dispatch/middleware/notifications.rb index c3776d53a8..ce3732b740 100644 --- a/actionpack/lib/action_dispatch/middleware/notifications.rb +++ b/actionpack/lib/action_dispatch/middleware/notifications.rb @@ -9,7 +9,9 @@ module ActionDispatch end def call(env) - payload = retrieve_payload_from_env(env) + request = Request.new(env) + payload = retrieve_payload_from_env(request.filter_env) + ActiveSupport::Notifications.instrument("action_dispatch.before_dispatch", payload) ActiveSupport::Notifications.instrument!("action_dispatch.after_dispatch", payload) do @@ -21,11 +23,10 @@ module ActionDispatch raise exception end - protected - - # Remove any rack related constants from the env, like rack.input. - def retrieve_payload_from_env(env) - Hash[:env => env.except(*env.keys.select { |k| k.to_s.index("rack.") == 0 })] - end + protected + # Remove any rack related constants from the env, like rack.input. + def retrieve_payload_from_env(env) + Hash[:env => env.except(*env.keys.select { |k| k.to_s.index("rack.") == 0 })] + end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_dispatch/routing/route.rb b/actionpack/lib/action_dispatch/routing/route.rb index f1431e7a37..e6e44d3546 100644 --- a/actionpack/lib/action_dispatch/routing/route.rb +++ b/actionpack/lib/action_dispatch/routing/route.rb @@ -44,6 +44,12 @@ module ActionDispatch def to_a [@app, @conditions, @defaults, @name] end + + def to_s + @to_s ||= begin + "%-6s %-40s %s" % [(verb || :any).to_s.upcase, path, requirements.inspect] + end + end end end end diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index d4c9df7ebd..2093bb3a0e 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -240,9 +240,9 @@ module ActionDispatch path = location.query ? "#{location.path}?#{location.query}" : location.path end - [ControllerCapture, ActionController::Testing].each do |mod| - unless ActionController::Base < mod - ActionController::Base.class_eval { include mod } + unless ActionController::Base < ActionController::Testing + ActionController::Base.class_eval do + include ActionController::Testing end end @@ -269,16 +269,15 @@ module ActionDispatch end session = Rack::Test::Session.new(@mock_session) - - @controller = ActionController::Base.capture_instantiation do - session.request(path, env) - end + session.request(path, env) @request_count += 1 @request = ActionDispatch::Request.new(session.last_request.env) @response = ActionDispatch::TestResponse.from_response(@mock_session.last_response) @html_document = nil + @controller = session.last_request.env['action_controller.instance'] + return response.status end @@ -296,31 +295,6 @@ module ActionDispatch end end - # A module used to extend ActionController::Base, so that integration tests - # can capture the controller used to satisfy a request. - module ControllerCapture #:nodoc: - extend ActiveSupport::Concern - - included do - alias_method_chain :initialize, :capture - end - - def initialize_with_capture(*args) - initialize_without_capture - self.class.last_instantiation ||= self - end - - module ClassMethods #:nodoc: - mattr_accessor :last_instantiation - - def capture_instantiation - self.last_instantiation = nil - yield - return last_instantiation - end - end - end - module Runner def app @app diff --git a/actionpack/test/controller/translation_test.rb b/actionpack/test/abstract/translation_test.rb index 0bf61a6556..0bf61a6556 100644 --- a/actionpack/test/controller/translation_test.rb +++ b/actionpack/test/abstract/translation_test.rb diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 8f2ea10206..bc1b0bde31 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -556,7 +556,7 @@ module ActiveRecord #:nodoc: end alias :colorize_logging= :colorize_logging - delegate :find, :first, :last, :all, :to => :scoped + delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped @@ -586,40 +586,6 @@ module ActiveRecord #:nodoc: connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) } end - # Returns true if a record exists in the table that matches the +id+ or - # conditions given, or false otherwise. The argument can take five forms: - # - # * Integer - Finds the record with this primary key. - # * String - Finds the record with a primary key corresponding to this - # string (such as <tt>'5'</tt>). - # * Array - Finds the record that matches these +find+-style conditions - # (such as <tt>['color = ?', 'red']</tt>). - # * Hash - Finds the record that matches these +find+-style conditions - # (such as <tt>{:color => 'red'}</tt>). - # * No args - Returns false if the table is empty, true otherwise. - # - # For more information about specifying conditions as a Hash or Array, - # see the Conditions section in the introduction to ActiveRecord::Base. - # - # Note: You can't pass in a condition as a string (like <tt>name = - # 'Jamie'</tt>), since it would be sanitized and then queried against - # the primary key column, like <tt>id = 'name = \'Jamie\''</tt>. - # - # ==== Examples - # Person.exists?(5) - # Person.exists?('5') - # Person.exists?(:name => "David") - # Person.exists?(['name LIKE ?', "%#{query}%"]) - # Person.exists? - def exists?(id_or_conditions = nil) - case id_or_conditions - when Array, Hash - where(id_or_conditions).exists? - else - scoped.exists?(id_or_conditions) - end - end - # Creates an object (or multiple objects) and saves it to the database, if validations pass. # The resulting object is returned whether the object was saved successfully to the database or not. # @@ -653,177 +619,6 @@ module ActiveRecord #:nodoc: end end - # Updates an object (or multiple objects) and saves it to the database, if validations pass. - # The resulting object is returned whether the object was saved successfully to the database or not. - # - # ==== Parameters - # - # * +id+ - This should be the id or an array of ids to be updated. - # * +attributes+ - This should be a hash of attributes to be set on the object, or an array of hashes. - # - # ==== Examples - # - # # Updating one record: - # Person.update(15, :user_name => 'Samuel', :group => 'expert') - # - # # Updating multiple records: - # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } } - # Person.update(people.keys, people.values) - def update(id, attributes) - if id.is_a?(Array) - idx = -1 - id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) } - else - object = find(id) - object.update_attributes(attributes) - object - end - end - - # Deletes the row with a primary key matching the +id+ argument, using a - # SQL +DELETE+ statement, and returns the number of rows deleted. Active - # Record objects are not instantiated, so the object's callbacks are not - # executed, including any <tt>:dependent</tt> association options or - # Observer methods. - # - # You can delete multiple rows at once by passing an Array of <tt>id</tt>s. - # - # Note: Although it is often much faster than the alternative, - # <tt>#destroy</tt>, skipping callbacks might bypass business logic in - # your application that ensures referential integrity or performs other - # essential jobs. - # - # ==== Examples - # - # # Delete a single row - # Todo.delete(1) - # - # # Delete multiple rows - # Todo.delete([2,3,4]) - def delete(id_or_array) - scoped.delete(id_or_array) - end - - # Destroy an object (or multiple objects) that has the given id, the object is instantiated first, - # therefore all callbacks and filters are fired off before the object is deleted. This method is - # less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run. - # - # This essentially finds the object (or multiple objects) with the given id, creates a new object - # from the attributes, and then calls destroy on it. - # - # ==== Parameters - # - # * +id+ - Can be either an Integer or an Array of Integers. - # - # ==== Examples - # - # # Destroy a single object - # Todo.destroy(1) - # - # # Destroy multiple objects - # todos = [1,2,3] - # Todo.destroy(todos) - def destroy(id) - if id.is_a?(Array) - id.map { |one_id| destroy(one_id) } - else - find(id).destroy - 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 - # or validations. - # - # ==== Parameters - # - # * +updates+ - A string, array, or hash representing the SET part of an SQL statement. - # * +conditions+ - A string, array, or hash representing the WHERE part of an SQL statement. See conditions in the intro. - # * +options+ - Additional options are <tt>:limit</tt> and <tt>:order</tt>, see the examples for usage. - # - # ==== Examples - # - # # Update all customers with the given attributes - # Customer.update_all :wants_email => true - # - # # Update all books with 'Rails' in their title - # Book.update_all "author = 'David'", "title LIKE '%Rails%'" - # - # # Update all avatars migrated more than a week ago - # Avatar.update_all ['migrated_at = ?', Time.now.utc], ['migrated_at > ?', 1.week.ago] - # - # # Update all books that match our conditions, but limit it to 5 ordered by date - # Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5 - def update_all(updates, conditions = nil, options = {}) - relation = unscoped - - relation = relation.where(conditions) if conditions - relation = relation.limit(options[:limit]) if options[:limit].present? - relation = relation.order(options[:order]) if options[:order].present? - - if current_scoped_methods && current_scoped_methods.limit_value.present? && current_scoped_methods.order_values.present? - # Only take order from scope if limit is also provided by scope, this - # is useful for updating a has_many association with a limit. - relation = current_scoped_methods.merge(relation) if current_scoped_methods - else - relation = current_scoped_methods.except(:limit, :order).merge(relation) if current_scoped_methods - end - - relation.update(sanitize_sql_for_assignment(updates)) - end - - # Destroys the records matching +conditions+ by instantiating each - # record and calling its +destroy+ method. Each object's callbacks are - # executed (including <tt>:dependent</tt> association options and - # +before_destroy+/+after_destroy+ Observer methods). Returns the - # collection of objects that were destroyed; each will be frozen, to - # reflect that no changes should be made (since they can't be - # persisted). - # - # Note: Instantiation, callback execution, and deletion of each - # record can be time consuming when you're removing many records at - # once. It generates at least one SQL +DELETE+ query per record (or - # possibly more, to enforce your callbacks). If you want to delete many - # rows quickly, without concern for their associations or callbacks, use - # +delete_all+ instead. - # - # ==== Parameters - # - # * +conditions+ - A string, array, or hash that specifies which records - # to destroy. If omitted, all records are destroyed. See the - # Conditions section in the introduction to ActiveRecord::Base for - # more information. - # - # ==== Examples - # - # Person.destroy_all("last_login < '2004-04-04'") - # Person.destroy_all(:status => "inactive") - def destroy_all(conditions = nil) - where(conditions).destroy_all - end - - # Deletes the records matching +conditions+ without instantiating the records first, and hence not - # calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that - # goes straight to the database, much more efficient than +destroy_all+. Be careful with relations - # though, in particular <tt>:dependent</tt> rules defined on associations are not honored. Returns - # the number of rows affected. - # - # ==== Parameters - # - # * +conditions+ - Conditions are specified the same way as with +find+ method. - # - # ==== Example - # - # Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')") - # Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else']) - # - # Both calls delete the affected posts all at once with a single DELETE statement. If you need to destroy dependent - # associations or call your <tt>before_*</tt> or +after_destroy+ callbacks, use the +destroy_all+ method instead. - def delete_all(conditions = nil) - where(conditions).delete_all - end - # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part. # The use of this method should be restricted to complicated SQL queries that can't be executed # using the ActiveRecord::Calculations class methods. Look into those before using this. @@ -1111,6 +906,10 @@ module ActiveRecord #:nodoc: reset_table_name end + def quoted_table_name + @quoted_table_name ||= connection.quote_table_name(table_name) + end + def reset_table_name #:nodoc: base = base_class @@ -1128,6 +927,7 @@ module ActiveRecord #:nodoc: name = "#{table_name_prefix}#{contained}#{undecorated_table_name(base.name)}#{table_name_suffix}" end + @quoted_table_name = nil set_table_name(name) name end @@ -1374,20 +1174,6 @@ module ActiveRecord #:nodoc: store_full_sti_class ? name : name.demodulize end - # Merges conditions so that the result is a valid +condition+ - def merge_conditions(*conditions) - segments = [] - - conditions.each do |condition| - unless condition.blank? - sql = sanitize_sql(condition) - segments << sql unless sql.blank? - end - end - - "(#{segments.join(') AND (')})" unless segments.empty? - end - def unscoped @unscoped ||= Relation.new(self, arel_table) finder_needs_type_condition? ? @unscoped.where(type_condition) : @unscoped @@ -2324,7 +2110,7 @@ module ActiveRecord #:nodoc: def update(attribute_names = @attributes.keys) attributes_with_values = arel_attributes_values(false, false, attribute_names) return 0 if attributes_with_values.empty? - self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).update(attributes_with_values) + self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values) end # Creates a record with values matching those of the instance attributes @@ -2548,10 +2334,6 @@ module ActiveRecord #:nodoc: hash.inject([]) { |list, pair| list << "#{pair.first} = #{pair.last}" }.join(", ") end - def self.quoted_table_name - self.connection.quote_table_name(self.table_name) - end - def quote_columns(quoter, hash) hash.inject({}) do |quoted, (name, value)| quoted[quoter.quote_column_name(name)] = value diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index bf0683eb8f..9044ca418b 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -84,7 +84,7 @@ module ActiveRecord relation.table[self.class.primary_key].eq(quoted_id).and( relation.table[self.class.locking_column].eq(quote_value(previous_value)) ) - ).update(arel_attributes_values(false, false, attribute_names)) + ).arel.update(arel_attributes_values(false, false, attribute_names)) unless affected_rows == 1 diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index d606934dce..ff6c041ef4 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -165,7 +165,7 @@ module ActiveRecord end def ==(other) - other.respond_to?(:to_a) ? to_a == other.to_a : false + other.respond_to?(:to_ary) ? to_a == other.to_a : false end private diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index decde50427..c9fff15199 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -8,7 +8,7 @@ module ActiveRecord include FinderMethods, Calculations, SpawnMethods, QueryMethods delegate :length, :collect, :map, :each, :all?, :include?, :to => :to_a - delegate :insert, :update, :to => :arel + delegate :insert, :to => :arel attr_reader :table, :klass @@ -80,19 +80,177 @@ module ActiveRecord if block_given? to_a.many? { |*block_args| yield(*block_args) } else - arel.send(:taken).present? ? to_a.many? : size > 1 + @limit_value.present? ? to_a.many? : size > 1 end end - def destroy_all - to_a.each {|object| object.destroy} - reset + # 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 + # or validations. + # + # ==== Parameters + # + # * +updates+ - A string, array, or hash representing the SET part of an SQL statement. + # * +conditions+ - A string, array, or hash representing the WHERE part of an SQL statement. See conditions in the intro. + # * +options+ - Additional options are <tt>:limit</tt> and <tt>:order</tt>, see the examples for usage. + # + # ==== Examples + # + # # Update all customers with the given attributes + # Customer.update_all :wants_email => true + # + # # Update all books with 'Rails' in their title + # Book.update_all "author = 'David'", "title LIKE '%Rails%'" + # + # # Update all avatars migrated more than a week ago + # Avatar.update_all ['migrated_at = ?', Time.now.utc], ['migrated_at > ?', 1.week.ago] + # + # # Update all books that match our conditions, but limit it to 5 ordered by date + # Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5 + def update_all(updates, conditions = nil, options = {}) + if conditions || options.present? + where(conditions).apply_finder_options(options.slice(:limit, :order)).update_all(updates) + else + # Apply limit and order only if they're both present + if @limit_value.present? == @order_values.present? + arel.update(@klass.send(:sanitize_sql_for_assignment, updates)) + else + except(:limit, :order).update_all(updates) + end + end + end + + # Updates an object (or multiple objects) and saves it to the database, if validations pass. + # The resulting object is returned whether the object was saved successfully to the database or not. + # + # ==== Parameters + # + # * +id+ - This should be the id or an array of ids to be updated. + # * +attributes+ - This should be a hash of attributes to be set on the object, or an array of hashes. + # + # ==== Examples + # + # # Updating one record: + # Person.update(15, :user_name => 'Samuel', :group => 'expert') + # + # # Updating multiple records: + # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } } + # Person.update(people.keys, people.values) + def update(id, attributes) + if id.is_a?(Array) + idx = -1 + id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) } + else + object = find(id) + object.update_attributes(attributes) + object + end + end + + # Destroys the records matching +conditions+ by instantiating each + # record and calling its +destroy+ method. Each object's callbacks are + # executed (including <tt>:dependent</tt> association options and + # +before_destroy+/+after_destroy+ Observer methods). Returns the + # collection of objects that were destroyed; each will be frozen, to + # reflect that no changes should be made (since they can't be + # persisted). + # + # Note: Instantiation, callback execution, and deletion of each + # record can be time consuming when you're removing many records at + # once. It generates at least one SQL +DELETE+ query per record (or + # possibly more, to enforce your callbacks). If you want to delete many + # rows quickly, without concern for their associations or callbacks, use + # +delete_all+ instead. + # + # ==== Parameters + # + # * +conditions+ - A string, array, or hash that specifies which records + # to destroy. If omitted, all records are destroyed. See the + # Conditions section in the introduction to ActiveRecord::Base for + # more information. + # + # ==== Examples + # + # Person.destroy_all("last_login < '2004-04-04'") + # Person.destroy_all(:status => "inactive") + def destroy_all(conditions = nil) + if conditions + where(conditions).destroy_all + else + to_a.each {|object| object.destroy} + reset + end + end + + # Destroy an object (or multiple objects) that has the given id, the object is instantiated first, + # therefore all callbacks and filters are fired off before the object is deleted. This method is + # less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run. + # + # This essentially finds the object (or multiple objects) with the given id, creates a new object + # from the attributes, and then calls destroy on it. + # + # ==== Parameters + # + # * +id+ - Can be either an Integer or an Array of Integers. + # + # ==== Examples + # + # # Destroy a single object + # Todo.destroy(1) + # + # # Destroy multiple objects + # todos = [1,2,3] + # Todo.destroy(todos) + def destroy(id) + if id.is_a?(Array) + id.map { |one_id| destroy(one_id) } + else + find(id).destroy + end end - def delete_all - arel.delete.tap { reset } + # Deletes the records matching +conditions+ without instantiating the records first, and hence not + # calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that + # goes straight to the database, much more efficient than +destroy_all+. Be careful with relations + # though, in particular <tt>:dependent</tt> rules defined on associations are not honored. Returns + # the number of rows affected. + # + # ==== Parameters + # + # * +conditions+ - Conditions are specified the same way as with +find+ method. + # + # ==== Example + # + # Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')") + # Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else']) + # + # Both calls delete the affected posts all at once with a single DELETE statement. If you need to destroy dependent + # associations or call your <tt>before_*</tt> or +after_destroy+ callbacks, use the +destroy_all+ method instead. + def delete_all(conditions = nil) + conditions ? where(conditions).delete_all : arel.delete.tap { reset } end + # Deletes the row with a primary key matching the +id+ argument, using a + # SQL +DELETE+ statement, and returns the number of rows deleted. Active + # Record objects are not instantiated, so the object's callbacks are not + # executed, including any <tt>:dependent</tt> association options or + # Observer methods. + # + # You can delete multiple rows at once by passing an Array of <tt>id</tt>s. + # + # Note: Although it is often much faster than the alternative, + # <tt>#destroy</tt>, skipping callbacks might bypass business logic in + # your application that ensures referential integrity or performs other + # essential jobs. + # + # ==== Examples + # + # # Delete a single row + # Todo.delete(1) + # + # # Delete multiple rows + # Todo.delete([2,3,4]) def delete(id_or_array) where(@klass.primary_key => id_or_array).delete_all end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 999309d2bd..d6d3d66642 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -119,10 +119,40 @@ module ActiveRecord args.any? ? apply_finder_options(args.first).to_a : to_a end + # Returns true if a record exists in the table that matches the +id+ or + # conditions given, or false otherwise. The argument can take five forms: + # + # * Integer - Finds the record with this primary key. + # * String - Finds the record with a primary key corresponding to this + # string (such as <tt>'5'</tt>). + # * Array - Finds the record that matches these +find+-style conditions + # (such as <tt>['color = ?', 'red']</tt>). + # * Hash - Finds the record that matches these +find+-style conditions + # (such as <tt>{:color => 'red'}</tt>). + # * No args - Returns false if the table is empty, true otherwise. + # + # For more information about specifying conditions as a Hash or Array, + # see the Conditions section in the introduction to ActiveRecord::Base. + # + # Note: You can't pass in a condition as a string (like <tt>name = + # 'Jamie'</tt>), since it would be sanitized and then queried against + # the primary key column, like <tt>id = 'name = \'Jamie\''</tt>. + # + # ==== Examples + # Person.exists?(5) + # Person.exists?('5') + # Person.exists?(:name => "David") + # Person.exists?(['name LIKE ?', "%#{query}%"]) + # Person.exists? def exists?(id = nil) - relation = select(primary_key).limit(1) - relation = relation.where(primary_key.eq(id)) if id - relation.first ? true : false + case id + when Array, Hash + where(id).exists? + else + relation = select(primary_key).limit(1) + relation = relation.where(primary_key.eq(id)) if id + relation.first ? true : false + end end protected @@ -242,15 +272,15 @@ module ActiveRecord result = where(primary_key.in(ids)).all expected_size = - if arel.taken && ids.size > arel.taken - arel.taken + if @limit_value && ids.size > @limit_value + @limit_value else ids.size end # 11 ids with limit 3, offset 9 should give 2 results. - if arel.skipped && (ids.size - arel.skipped < expected_size) - expected_size = ids.size - arel.skipped + if @offset_value && (ids.size - @offset_value < expected_size) + expected_size = ids.size - @offset_value end if result.size == expected_size diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 163d698b5c..d0689cd93e 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -140,16 +140,18 @@ module ActiveRecord selects = @select_values.uniq + quoted_table_name = @klass.quoted_table_name + if selects.present? selects.each do |s| @implicit_readonly = false arel = arel.project(s) if s.present? end - elsif joins.present? - arel = arel.project(@klass.quoted_table_name + '.*') + else + arel = arel.project(quoted_table_name + '.*') end - arel = arel.from(@from_value) if @from_value.present? + arel = @from_value.present? ? arel.from(@from_value) : arel.from(quoted_table_name) case @lock_value when TrueClass @@ -167,8 +169,8 @@ module ActiveRecord builder = PredicateBuilder.new(table.engine) conditions = if [String, Array].include?(args.first.class) - merged = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first) - Arel::SqlLiteral.new(merged) if merged + sql = @klass.send(:sanitize_sql, args.size > 1 ? args : args.first) + Arel::SqlLiteral.new("(#{sql})") if sql.present? elsif args.first.is_a?(Hash) attributes = @klass.send(:expand_hash_conditions_for_aggregates, args.first) builder.build_from_hash(attributes, table) diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 1577a9b116..cccf413e67 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -1,17 +1,7 @@ module ActiveRecord module SpawnMethods - def spawn(arel_table = self.table) - relation = self.class.new(@klass, arel_table) - - (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).each do |query_method| - relation.send(:"#{query_method}_values=", send(:"#{query_method}_values")) - end - - Relation::SINGLE_VALUE_METHODS.each do |query_method| - relation.send(:"#{query_method}_value=", send(:"#{query_method}_value")) - end - - relation + def spawn + clone.reset end def merge(r) diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index b92a7ff129..743681359c 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -93,13 +93,12 @@ module Rails initializers end - # TODO: Fix this method + # TODO: Fix this method. It loads all railties independent if :all is given + # or not, otherwise frameworks are never loaded. def plugins @plugins ||= begin plugin_names = (config.plugins || [:all]).map { |p| p.to_sym } - Railtie.plugins.select { |p| - plugin_names.include?(:all) || plugin_names.include?(p.plugin_name) - }.map { |p| p.new } + Plugin.all(plugin_names, config.paths.vendor.plugins) + Railtie.plugins.map(&:new) + Plugin.all(plugin_names, config.paths.vendor.plugins) end end |