From fb61fbd35229154f4ced124568697878822336cb Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Tue, 17 Nov 2009 15:35:35 -0800 Subject: Revert "Ensure Model#destroy respects optimistic locking" [#1966 state:open] This reverts commit 0d922885fb54c19f04680482f024452859218910. Conflicts: activerecord/lib/active_record/locking/optimistic.rb --- .../lib/active_record/locking/optimistic.rb | 34 ---------------------- 1 file changed, 34 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index c8cd79a2b0..986bc7009b 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -23,16 +23,6 @@ module ActiveRecord # p2.first_name = "should fail" # p2.save # Raises a ActiveRecord::StaleObjectError # - # Optimistic locking will also check for stale data when objects are destroyed. Example: - # - # p1 = Person.find(1) - # p2 = Person.find(1) - # - # p1.first_name = "Michael" - # p1.save - # - # p2.destroy # Raises a ActiveRecord::StaleObjectError - # # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging, # or otherwise apply the business logic needed to resolve the conflict. # @@ -49,7 +39,6 @@ module ActiveRecord self.lock_optimistically = true alias_method_chain :update, :lock - alias_method_chain :destroy, :lock alias_method_chain :attributes_from_column_definition, :lock class << self @@ -111,29 +100,6 @@ module ActiveRecord end end - def destroy_with_lock #:nodoc: - return destroy_without_lock unless locking_enabled? - - unless new_record? - lock_col = self.class.locking_column - previous_value = send(lock_col).to_i - - arel_table = self.class.arel_table(self.class.table_name) - - affected_rows = arel_table.where( - arel_table[self.class.primary_key].eq(quoted_id).and( - arel_table[self.class.locking_column].eq(quote_value(previous_value)) - ) - ).delete - - unless affected_rows == 1 - raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object" - end - end - - freeze - end - module ClassMethods DEFAULT_LOCKING_COLUMN = 'lock_version' -- cgit v1.2.3 From ea290e77e6c50b13a0c9000eceaa5412de6918bc Mon Sep 17 00:00:00 2001 From: Gabe da Silveira Date: Tue, 17 Nov 2009 09:36:23 -0800 Subject: Insert generated association members in the same order they are specified when assigning to a has_many :through using the generated *_ids method [#3491 state:committed] Signed-off-by: Jeremy Kemper --- activerecord/lib/active_record/associations.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 6c5e25010f..fc6f15206a 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/module/delegation' +require 'active_support/core_ext/enumerable' module ActiveRecord class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc: @@ -1396,8 +1397,8 @@ module ActiveRecord end define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value| - ids = (new_value || []).reject { |nid| nid.blank? } - send("#{reflection.name}=", reflection.klass.find(ids)) + ids = (new_value || []).reject { |nid| nid.blank? }.map(&:to_i) + send("#{reflection.name}=", reflection.klass.find(ids).index_by(&:id).values_at(*ids)) end end end -- cgit v1.2.3 From 78790e47b8603917e2f2352f973a2de7769cb74b Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Mon, 23 Nov 2009 10:05:35 -0800 Subject: Revert "Revert "Assert primary key does not exist in habtm when the association is defined, instead of doing that everytime a record is inserted."" This reverts commit 2b82708b0efb3a3458e8177beab58f0c585788ae. [#3128 state:resolved] Conflicts: activerecord/lib/active_record/associations.rb activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb --- activerecord/lib/active_record/associations.rb | 10 +++++++++- .../associations/has_and_belongs_to_many_association.rb | 14 +------------- 2 files changed, 10 insertions(+), 14 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index fc6f15206a..0fcd288fc5 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -61,6 +61,12 @@ module ActiveRecord end end + class HasAndBelongsToManyAssociationWithPrimaryKeyError < ActiveRecordError #:nodoc: + def initialize(reflection) + super("Primary key is not allowed in a has_and_belongs_to_many join table (#{reflection.options[:join_table]}).") + end + end + class HasAndBelongsToManyAssociationForeignKeyNeeded < ActiveRecordError #:nodoc: def initialize(reflection) super("Cannot create self referential has_and_belongs_to_many association on '#{reflection.class_name rescue nil}##{reflection.name rescue nil}'. :association_foreign_key cannot be the same as the :foreign_key.") @@ -1675,7 +1681,6 @@ module ActiveRecord def create_has_and_belongs_to_many_reflection(association_id, options, &extension) options.assert_valid_keys(valid_keys_for_has_and_belongs_to_many_association) - options[:extend] = create_extension_modules(association_id, extension, options[:extend]) reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self) @@ -1685,6 +1690,9 @@ module ActiveRecord end reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name)) + if connection.supports_primary_key? && (connection.primary_key(reflection.options[:join_table]) rescue false) + raise HasAndBelongsToManyAssociationWithPrimaryKeyError.new(reflection) + end reflection end diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index c646fe488b..b01faa5212 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -1,11 +1,6 @@ module ActiveRecord module Associations class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc: - def initialize(owner, reflection) - super - @primary_key_list = {} - end - def create(attributes = {}) create_record(attributes) { |record| insert_record(record) } end @@ -23,9 +18,7 @@ module ActiveRecord end def has_primary_key? - return @has_primary_key unless @has_primary_key.nil? - @has_primary_key = (@owner.connection.supports_primary_key? && - @owner.connection.primary_key(@reflection.options[:join_table])) + @has_primary_key ||= @owner.connection.supports_primary_key? && @owner.connection.primary_key(@reflection.options[:join_table]) end protected @@ -40,11 +33,6 @@ module ActiveRecord end def insert_record(record, force = true, validate = true) - if has_primary_key? - raise ActiveRecord::ConfigurationError, - "Primary key is not allowed in a has_and_belongs_to_many join table (#{@reflection.options[:join_table]})." - end - if record.new_record? if force record.save! -- cgit v1.2.3 From 50c28e78c7aa40dc329facbe6131d657d5629bd4 Mon Sep 17 00:00:00 2001 From: Mike Breen Date: Wed, 20 May 2009 10:31:12 -0400 Subject: Implement ActiveRecord#reset_counter_cache [#1211 state:committed] Signed-off-by: Jeremy Kemper --- activerecord/lib/active_record/base.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 056f29f029..e04684dc43 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -967,6 +967,24 @@ module ActiveRecord #:nodoc: connection.select_value(sql, "#{name} Count").to_i end + # Reset a counter cache for all records. + # + # ==== Parameters + # + # * +association_name+ - The name of of the association counter cache to reset + # + # ==== Examples + # # For all Post records reset the comments_count + # Post.reset_counter_cache(:comments) + def reset_counter_cache(association) + child_class = reflect_on_association(association).klass + counter_name = child_class.reflect_on_association(self.name.downcase.to_sym).counter_cache_column + + find_each do |object| + connection.update("UPDATE #{quoted_table_name} SET #{connection.quote_column_name(counter_name)} = #{object.send(association).count} WHERE #{connection.quote_column_name(primary_key)} = #{quote_value(object.id)}", "#{name} UPDATE") + end + end + # A generic "counter updater" implementation, intended primarily to be # used by increment_counter and decrement_counter, but which may also # be useful on its own. It simply does a direct SQL update for the record -- cgit v1.2.3 From 49e943c4f0ac3459bd53023167aaa08fc8e46733 Mon Sep 17 00:00:00 2001 From: Mat Brown Date: Thu, 22 Oct 2009 10:20:44 -0400 Subject: Fix instance_eval calls to association proxies In the current stable, ActiveRecord::Associations::AssociationProxy#method_missing calls yield() if a block is given, causing the block to always be evaluated in its calling context. However, in the case of instance_eval, correct behavior requires that the block be passed directly to the @target, rather than being evaluated inside a different block. Incidentally, this also simplifies the code slightly. [#3412 state:committed] Signed-off-by: Jeremy Kemper --- activerecord/lib/active_record/associations/association_proxy.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 7d8f4670fa..6ad1e06300 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -200,18 +200,14 @@ module ActiveRecord private # Forwards any missing method call to the \target. - def method_missing(method, *args) + def method_missing(method, *args, &block) if load_target unless @target.respond_to?(method) message = "undefined method `#{method.to_s}' for \"#{@target}\":#{@target.class.to_s}" raise NoMethodError, message end - if block_given? - @target.send(method, *args) { |*block_args| yield(*block_args) } - else - @target.send(method, *args) - end + @target.send(method, *args, &block) end end -- cgit v1.2.3 From c1304098cca8a9247a9ad1461a1a343354650843 Mon Sep 17 00:00:00 2001 From: Carlhuda Date: Wed, 2 Dec 2009 20:01:01 -0800 Subject: Reorganize autoloads: * A new module (ActiveSupport::Autoload) is provide that extends autoloading with new behavior. * All autoloads in modules that have extended ActiveSupport::Autoload will be eagerly required in threadsafe environments * Autoloads can optionally leave off the path if the path is the same as full_constant_name.underscore * It is possible to specify that a group of autoloads live under an additional path. For instance, all of ActionDispatch's middlewares are ActionDispatch::MiddlewareName, but they live under "action_dispatch/middlewares/middleware_name" * It is possible to specify that a group of autoloads are all found at the same path. For instance, a number of exceptions might all be declared there. * One consequence of this is that testing-related constants are not autoloaded. To get the testing helpers for a given component, require "component_name/test_case". For instance, "action_controller/test_case". * test_help.rb, which is automatically required by a Rails application's test helper, requires the test_case.rb for all active components, so this change will not be disruptive in existing or new applications. --- activerecord/lib/active_record.rb | 100 ++++++++++++++++++++------------------ 1 file changed, 53 insertions(+), 47 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 8195e78826..f935d15526 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -32,64 +32,66 @@ require 'active_model' require 'arel' module ActiveRecord - # TODO: Review explicit loads to see if they will automatically be handled by the initializer. - def self.load_all! - [Base, DynamicFinderMatch, ConnectionAdapters::AbstractAdapter] - end + extend ActiveSupport::Autoload - autoload :VERSION, 'active_record/version' + autoload :VERSION autoload :ActiveRecordError, 'active_record/base' autoload :ConnectionNotEstablished, 'active_record/base' - autoload :Aggregations, 'active_record/aggregations' - autoload :AssociationPreload, 'active_record/association_preload' - autoload :Associations, 'active_record/associations' - autoload :AttributeMethods, 'active_record/attribute_methods' - autoload :Attributes, 'active_record/attributes' - autoload :AutosaveAssociation, 'active_record/autosave_association' - autoload :Relation, 'active_record/relation' - autoload :Base, 'active_record/base' - autoload :Batches, 'active_record/batches' - autoload :Calculations, 'active_record/calculations' - autoload :Callbacks, 'active_record/callbacks' - autoload :DynamicFinderMatch, 'active_record/dynamic_finder_match' - autoload :DynamicScopeMatch, 'active_record/dynamic_scope_match' - autoload :Migration, 'active_record/migration' + autoload :Aggregations + autoload :AssociationPreload + autoload :Associations + autoload :AttributeMethods + autoload :Attributes + autoload :AutosaveAssociation + autoload :Relation + autoload :Base + autoload :Batches + autoload :Calculations + autoload :Callbacks + autoload :DynamicFinderMatch + autoload :DynamicScopeMatch + autoload :Migration autoload :Migrator, 'active_record/migration' - autoload :NamedScope, 'active_record/named_scope' - autoload :NestedAttributes, 'active_record/nested_attributes' - autoload :Observer, 'active_record/observer' - autoload :QueryCache, 'active_record/query_cache' - autoload :Reflection, 'active_record/reflection' - autoload :Schema, 'active_record/schema' - autoload :SchemaDumper, 'active_record/schema_dumper' - autoload :Serialization, 'active_record/serialization' - autoload :SessionStore, 'active_record/session_store' - autoload :StateMachine, 'active_record/state_machine' - autoload :TestCase, 'active_record/test_case' - autoload :Timestamp, 'active_record/timestamp' - autoload :Transactions, 'active_record/transactions' - autoload :Types, 'active_record/types' - autoload :Validations, 'active_record/validations' + autoload :NamedScope + autoload :NestedAttributes + autoload :Observer + autoload :QueryCache + autoload :Reflection + autoload :Schema + autoload :SchemaDumper + autoload :Serialization + autoload :SessionStore + autoload :StateMachine + autoload :Timestamp + autoload :Transactions + autoload :Types + autoload :Validations module AttributeMethods - autoload :BeforeTypeCast, 'active_record/attribute_methods/before_type_cast' - autoload :Dirty, 'active_record/attribute_methods/dirty' - autoload :PrimaryKey, 'active_record/attribute_methods/primary_key' - autoload :Query, 'active_record/attribute_methods/query' - autoload :Read, 'active_record/attribute_methods/read' - autoload :TimeZoneConversion, 'active_record/attribute_methods/time_zone_conversion' - autoload :Write, 'active_record/attribute_methods/write' + extend ActiveSupport::Autoload + + autoload :BeforeTypeCast + autoload :Dirty + autoload :PrimaryKey + autoload :Query + autoload :Read + autoload :TimeZoneConversion + autoload :Write end module Attributes - autoload :Aliasing, 'active_record/attributes/aliasing' - autoload :Store, 'active_record/attributes/store' - autoload :Typecasting, 'active_record/attributes/typecasting' + extend ActiveSupport::Autoload + + autoload :Aliasing + autoload :Store + autoload :Typecasting end module Type + extend ActiveSupport::Autoload + autoload :Number, 'active_record/types/number' autoload :Object, 'active_record/types/object' autoload :Serialize, 'active_record/types/serialize' @@ -98,12 +100,16 @@ module ActiveRecord end module Locking - autoload :Optimistic, 'active_record/locking/optimistic' - autoload :Pessimistic, 'active_record/locking/pessimistic' + extend ActiveSupport::Autoload + + autoload :Optimistic + autoload :Pessimistic end module ConnectionAdapters - autoload :AbstractAdapter, 'active_record/connection_adapters/abstract_adapter' + extend ActiveSupport::Autoload + + autoload :AbstractAdapter end end -- cgit v1.2.3 From e55284e8256461fc2440c41548ee9b4216f96b47 Mon Sep 17 00:00:00 2001 From: Ben Marini Date: Sun, 27 Sep 2009 16:25:18 -0700 Subject: Add support for Mysql column positioning via #add_column and #change_column add_column and change_column in the Mysql adapter now accept some additional options: :first => true # Put the column in front of all the columns :after => column_name # Put the colmn after 'column_name' add_column :new_col, :string, :first => true add_column :another_col, :integer, :default => 0, :after => :new_col [#3286 state:committed] Signed-off-by: Jeremy Kemper --- .../active_record/connection_adapters/mysql_adapter.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index ad36ff22e3..fa28bc64df 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -470,6 +470,13 @@ module ActiveRecord execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}" end + def add_column(table_name, column_name, type, options = {}) + add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" + add_column_options!(add_column_sql, options) + add_column_position!(add_column_sql, options) + execute(add_column_sql) + end + def change_column_default(table_name, column_name, default) #:nodoc: column = column_for(table_name, column_name) change_column table_name, column_name, column.sql_type, :default => default @@ -498,6 +505,7 @@ module ActiveRecord change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" add_column_options!(change_column_sql, options) + add_column_position!(change_column_sql, options) execute(change_column_sql) end @@ -529,6 +537,13 @@ module ActiveRecord end end + def add_column_position!(sql, options) + if options[:first] + sql << " FIRST" + elsif options[:after] + sql << " AFTER #{quote_column_name(options[:after])}" + end + end # SHOW VARIABLES LIKE 'name' def show_variable(name) -- cgit v1.2.3 From 32395899d7c97f69b508b7d7f9b7711f28586679 Mon Sep 17 00:00:00 2001 From: Gabe da Silveira Date: Wed, 2 Dec 2009 15:28:54 -0800 Subject: Replace reset_counter_cache with reset_counters that has API inline with existing update_counters method [#1211 state:committed] Signed-off-by: Jeremy Kemper --- activerecord/lib/active_record/base.rb | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index e04684dc43..321bba466e 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -967,20 +967,25 @@ module ActiveRecord #:nodoc: connection.select_value(sql, "#{name} Count").to_i end - # Reset a counter cache for all records. + # Resets one or more counter caches to their correct value using an SQL + # count query. This is useful when adding new counter caches, or if the + # counter has been corrupted or modified directly by SQL. # # ==== Parameters # - # * +association_name+ - The name of of the association counter cache to reset + # * +id+ - The id of the object you wish to reset a counter on. + # * +counters+ - One or more counter names to reset # # ==== Examples - # # For all Post records reset the comments_count - # Post.reset_counter_cache(:comments) - def reset_counter_cache(association) - child_class = reflect_on_association(association).klass - counter_name = child_class.reflect_on_association(self.name.downcase.to_sym).counter_cache_column + # + # # For Post with id #1 records reset the comments_count + # Post.reset_counters(1, :comments) + def reset_counters(id, *counters) + object = find(id) + counters.each do |association| + child_class = reflect_on_association(association).klass + counter_name = child_class.reflect_on_association(self.name.downcase.to_sym).counter_cache_column - find_each do |object| connection.update("UPDATE #{quoted_table_name} SET #{connection.quote_column_name(counter_name)} = #{object.send(association).count} WHERE #{connection.quote_column_name(primary_key)} = #{quote_value(object.id)}", "#{name} UPDATE") end end -- cgit v1.2.3 From 8a50f8a545ebde9cbae4187d1b1d5a59a29711f3 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Sat, 12 Dec 2009 19:58:54 -0600 Subject: Revert "Fix instance_eval calls to association proxies" I think it may of broke the build. Lets see. This reverts commit 49e943c4f0ac3459bd53023167aaa08fc8e46733. Conflicts: activerecord/test/cases/associations/has_many_associations_test.rb --- activerecord/lib/active_record/associations/association_proxy.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 6ad1e06300..7d8f4670fa 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -200,14 +200,18 @@ module ActiveRecord private # Forwards any missing method call to the \target. - def method_missing(method, *args, &block) + def method_missing(method, *args) if load_target unless @target.respond_to?(method) message = "undefined method `#{method.to_s}' for \"#{@target}\":#{@target.class.to_s}" raise NoMethodError, message end - @target.send(method, *args, &block) + if block_given? + @target.send(method, *args) { |*block_args| yield(*block_args) } + else + @target.send(method, *args) + end end end -- cgit v1.2.3 From bf6af5f71917b5615edb5905729b22772133eea4 Mon Sep 17 00:00:00 2001 From: Will Date: Wed, 16 Dec 2009 10:49:06 -0600 Subject: When passing force_reload = true to an association, don't use the query cache [#1827 state:resolved] Signed-off-by: Joshua Peek --- activerecord/lib/active_record/associations.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 0fcd288fc5..8dcb3a7711 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1325,7 +1325,7 @@ module ActiveRecord if association.nil? || force_reload association = association_proxy_class.new(self, reflection) - retval = association.reload + retval = force_reload ? reflection.klass.uncached { association.reload } : association.reload if retval.nil? and association_proxy_class == BelongsToAssociation association_instance_set(reflection.name, nil) return nil @@ -1370,7 +1370,7 @@ module ActiveRecord association_instance_set(reflection.name, association) end - association.reload if force_reload + reflection.klass.uncached { association.reload } if force_reload association end -- cgit v1.2.3 From 7ee5843c3cedfe36a680d5b28aa31eef45296c50 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Wed, 16 Dec 2009 11:56:51 -0600 Subject: Fully expand relative rails framework paths and make sure we aren't adding any to the load path more than once. --- activerecord/lib/active_record.rb | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index f935d15526..2376bbd04a 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -21,11 +21,12 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib" -$:.unshift(activesupport_path) if File.directory?(activesupport_path) -activemodel_path = "#{File.dirname(__FILE__)}/../../activemodel/lib" -$:.unshift(activemodel_path) if File.directory?(activemodel_path) +activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__) +$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) + +activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__) +$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path) require 'active_support' require 'active_model' @@ -91,7 +92,7 @@ module ActiveRecord module Type extend ActiveSupport::Autoload - + autoload :Number, 'active_record/types/number' autoload :Object, 'active_record/types/object' autoload :Serialize, 'active_record/types/serialize' @@ -101,14 +102,14 @@ module ActiveRecord module Locking extend ActiveSupport::Autoload - + autoload :Optimistic autoload :Pessimistic end module ConnectionAdapters extend ActiveSupport::Autoload - + autoload :AbstractAdapter end end -- cgit v1.2.3 From ace20bd25e3818b7f29c222643dd445c48b36425 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Tue, 22 Dec 2009 17:27:37 -0600 Subject: Flip deferrable autoload convention --- activerecord/lib/active_record.rb | 116 +++++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 52 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 2376bbd04a..196b87c0ac 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -35,82 +35,94 @@ require 'arel' module ActiveRecord extend ActiveSupport::Autoload - autoload :VERSION - - autoload :ActiveRecordError, 'active_record/base' - autoload :ConnectionNotEstablished, 'active_record/base' - - autoload :Aggregations - autoload :AssociationPreload - autoload :Associations - autoload :AttributeMethods - autoload :Attributes - autoload :AutosaveAssociation - autoload :Relation - autoload :Base - autoload :Batches - autoload :Calculations - autoload :Callbacks - autoload :DynamicFinderMatch - autoload :DynamicScopeMatch - autoload :Migration - autoload :Migrator, 'active_record/migration' - autoload :NamedScope - autoload :NestedAttributes - autoload :Observer - autoload :QueryCache - autoload :Reflection - autoload :Schema - autoload :SchemaDumper - autoload :Serialization - autoload :SessionStore - autoload :StateMachine - autoload :Timestamp - autoload :Transactions - autoload :Types - autoload :Validations + eager_autoload do + autoload :VERSION + + autoload :ActiveRecordError, 'active_record/base' + autoload :ConnectionNotEstablished, 'active_record/base' + + autoload :Aggregations + autoload :AssociationPreload + autoload :Associations + autoload :AttributeMethods + autoload :Attributes + autoload :AutosaveAssociation + autoload :Relation + autoload :Base + autoload :Batches + autoload :Calculations + autoload :Callbacks + autoload :DynamicFinderMatch + autoload :DynamicScopeMatch + autoload :Migration + autoload :Migrator, 'active_record/migration' + autoload :NamedScope + autoload :NestedAttributes + autoload :Observer + autoload :QueryCache + autoload :Reflection + autoload :Schema + autoload :SchemaDumper + autoload :Serialization + autoload :SessionStore + autoload :StateMachine + autoload :Timestamp + autoload :Transactions + autoload :Types + autoload :Validations + end module AttributeMethods extend ActiveSupport::Autoload - autoload :BeforeTypeCast - autoload :Dirty - autoload :PrimaryKey - autoload :Query - autoload :Read - autoload :TimeZoneConversion - autoload :Write + eager_autoload do + autoload :BeforeTypeCast + autoload :Dirty + autoload :PrimaryKey + autoload :Query + autoload :Read + autoload :TimeZoneConversion + autoload :Write + end end module Attributes extend ActiveSupport::Autoload - autoload :Aliasing - autoload :Store - autoload :Typecasting + eager_autoload do + autoload :Aliasing + autoload :Store + autoload :Typecasting + end end module Type extend ActiveSupport::Autoload - autoload :Number, 'active_record/types/number' - autoload :Object, 'active_record/types/object' - autoload :Serialize, 'active_record/types/serialize' - autoload :TimeWithZone, 'active_record/types/time_with_zone' - autoload :Unknown, 'active_record/types/unknown' + eager_autoload do + autoload :Number, 'active_record/types/number' + autoload :Object, 'active_record/types/object' + autoload :Serialize, 'active_record/types/serialize' + autoload :TimeWithZone, 'active_record/types/time_with_zone' + autoload :Unknown, 'active_record/types/unknown' + end end module Locking extend ActiveSupport::Autoload - autoload :Optimistic - autoload :Pessimistic + eager_autoload do + autoload :Optimistic + autoload :Pessimistic + end end module ConnectionAdapters extend ActiveSupport::Autoload - autoload :AbstractAdapter + eager_autoload do + autoload :AbstractAdapter + end end end -- cgit v1.2.3 From 44cd9e0e7132abe632664377f13f3edd1106685a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 23 Dec 2009 12:14:00 +0100 Subject: ActiveRecord::Validations are now built on top of Validator as well. --- .../lib/active_record/validations/associated.rb | 16 +-- .../lib/active_record/validations/uniqueness.rb | 134 ++++++++++++--------- 2 files changed, 85 insertions(+), 65 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb index 92f47d770f..6e6f4df415 100644 --- a/activerecord/lib/active_record/validations/associated.rb +++ b/activerecord/lib/active_record/validations/associated.rb @@ -1,5 +1,12 @@ module ActiveRecord module Validations + class AssociatedValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + return if (value.is_a?(Array) ? value : [value]).compact.all?{ |r| r.valid? } + record.errors.add(attribute, :invalid, :default => options[:message], :value => value) + end + end + module ClassMethods # Validates whether the associated object or objects are all valid themselves. Works with any kind of association. # @@ -33,13 +40,8 @@ module ActiveRecord # not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The # method, proc or string should return or evaluate to a true or false value. def validates_associated(*attr_names) - configuration = attr_names.extract_options! - - validates_each(attr_names, configuration) do |record, attr_name, value| - unless (value.is_a?(Array) ? value : [value]).collect { |r| r.nil? || r.valid? }.all? - record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value) - end - end + options = attr_names.extract_options! + validates_with AssociatedValidator, options.merge(:attributes => attr_names) end end end diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 711086dc2c..b3a34501dc 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -1,5 +1,77 @@ module ActiveRecord module Validations + class UniquenessValidator < ActiveModel::EachValidator + def initialize(options) + @klass = options.delete(:klass) + super(options.reverse_merge(:case_sensitive => true)) + end + + def validate_each(record, attribute, value) + finder_class = find_finder_class_for(record) + table_name = record.class.quoted_table_name + sql, params = mount_sql_and_params(finder_class, table_name, attribute, value) + + Array(options[:scope]).each do |scope_item| + scope_value = record.send(scope_item) + sql << " AND " << record.class.send(:attribute_condition, "#{table_name}.#{scope_item}", scope_value) + params << scope_value + end + + unless record.new_record? + sql << " AND #{record.class.quoted_table_name}.#{record.class.primary_key} <> ?" + params << record.send(:id) + end + + finder_class.with_exclusive_scope do + if finder_class.exists?([sql, *params]) + record.errors.add(attribute, :taken, :default => options[:message], :value => value) + end + end + end + + protected + + # The check for an existing value should be run from a class that + # isn't abstract. This means working down from the current class + # (self), to the first non-abstract class. Since classes don't know + # their subclasses, we have to build the hierarchy between self and + # the record's class. + def find_finder_class_for(record) #:nodoc: + class_hierarchy = [record.class] + + while class_hierarchy.first != @klass + class_hierarchy.insert(0, class_hierarchy.first.superclass) + end + + class_hierarchy.detect { |klass| !klass.abstract_class? } + end + + def mount_sql_and_params(klass, table_name, attribute, value) #:nodoc: + column = klass.columns_hash[attribute.to_s] + + operator = if value.nil? + "IS ?" + elsif column.text? + value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s + "#{klass.connection.case_sensitive_equality_operator} ?" + else + "= ?" + end + + sql_attribute = "#{table_name}.#{klass.connection.quote_column_name(attribute)}" + + if value.nil? || (options[:case_sensitive] || !column.text?) + sql = "#{sql_attribute} #{operator}" + params = [value] + else + sql = "LOWER(#{sql_attribute}) #{operator}" + params = [value.mb_chars.downcase] + end + + [sql, params] + end + end + module ClassMethods # Validates whether the value of the specified attributes are unique across the system. Useful for making sure that only one user # can be named "davidhh". @@ -69,6 +141,7 @@ module ActiveRecord # # This could even happen if you use transactions with the 'serializable' # isolation level. There are several ways to get around this problem: + # # - By locking the database table before validating, and unlocking it after # saving. However, table locking is very expensive, and thus not # recommended. @@ -94,65 +167,10 @@ module ActiveRecord # index constraint errors from other types of database errors, so you # will have to parse the (database-specific) exception message to detect # such a case. + # def validates_uniqueness_of(*attr_names) - configuration = { :case_sensitive => true } - configuration.update(attr_names.extract_options!) - - validates_each(attr_names,configuration) do |record, attr_name, value| - # The check for an existing value should be run from a class that - # isn't abstract. This means working down from the current class - # (self), to the first non-abstract class. Since classes don't know - # their subclasses, we have to build the hierarchy between self and - # the record's class. - class_hierarchy = [record.class] - while class_hierarchy.first != self - class_hierarchy.insert(0, class_hierarchy.first.superclass) - end - - # Now we can work our way down the tree to the first non-abstract - # class (which has a database table to query from). - finder_class = class_hierarchy.detect { |klass| !klass.abstract_class? } - - column = finder_class.columns_hash[attr_name.to_s] - - if value.nil? - comparison_operator = "IS ?" - elsif column.text? - comparison_operator = "#{connection.case_sensitive_equality_operator} ?" - value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s - else - comparison_operator = "= ?" - end - - sql_attribute = "#{record.class.quoted_table_name}.#{connection.quote_column_name(attr_name)}" - - if value.nil? || (configuration[:case_sensitive] || !column.text?) - condition_sql = "#{sql_attribute} #{comparison_operator}" - condition_params = [value] - else - condition_sql = "LOWER(#{sql_attribute}) #{comparison_operator}" - condition_params = [value.mb_chars.downcase] - end - - if scope = configuration[:scope] - Array(scope).map do |scope_item| - scope_value = record.send(scope_item) - condition_sql << " AND " << attribute_condition("#{record.class.quoted_table_name}.#{scope_item}", scope_value) - condition_params << scope_value - end - end - - unless record.new_record? - condition_sql << " AND #{record.class.quoted_table_name}.#{record.class.primary_key} <> ?" - condition_params << record.send(:id) - end - - finder_class.with_exclusive_scope do - if finder_class.exists?([condition_sql, *condition_params]) - record.errors.add(attr_name, :taken, :default => configuration[:message], :value => value) - end - end - end + options = attr_names.extract_options! + validates_with UniquenessValidator, options.merge(:attributes => attr_names, :klass => self) end end end -- cgit v1.2.3 From 74098e4cb6de01745db8f1d8d567645553ade7c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 23 Dec 2009 13:30:58 +0100 Subject: No need to use ValidationsRepairHelper hack on ActiveModel anymore, Model.reset_callbacks(:validate) is enough. However, tests in ActiveRecord are still coupled, so moved ValidationsRepairHelper back there. --- activerecord/lib/active_record/validations/associated.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb index 6e6f4df415..66b78682ad 100644 --- a/activerecord/lib/active_record/validations/associated.rb +++ b/activerecord/lib/active_record/validations/associated.rb @@ -2,7 +2,7 @@ module ActiveRecord module Validations class AssociatedValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) - return if (value.is_a?(Array) ? value : [value]).compact.all?{ |r| r.valid? } + return if (value.is_a?(Array) ? value : [value]).collect{ |r| r.nil? || r.valid? }.all? record.errors.add(attribute, :invalid, :default => options[:message], :value => value) end end -- cgit v1.2.3 From 38aeb1528c376f7a058beea6db0a328720b85f01 Mon Sep 17 00:00:00 2001 From: Carlhuda Date: Wed, 23 Dec 2009 14:55:12 -0800 Subject: Moving out some framework specific initializers into the framework libraries. --- activerecord/lib/active_record/rails.rb | 51 +++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 activerecord/lib/active_record/rails.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/rails.rb b/activerecord/lib/active_record/rails.rb new file mode 100644 index 0000000000..4071385563 --- /dev/null +++ b/activerecord/lib/active_record/rails.rb @@ -0,0 +1,51 @@ +# For now, action_controller must always be present with +# rails, so let's make sure that it gets required before +# here. This is needed for correctly setting up the middleware. +# In the future, this might become an optional require. +require "action_controller/rails" + +module ActiveRecord + class Plugin < Rails::Plugin + plugin_name :active_record + + initializer "active_record.set_configs" do |app| + app.config.active_record.each do |k,v| + ActiveRecord::Base.send "#{k}=", v + end + end + + # This sets the database configuration from Configuration#database_configuration + # and then establishes the connection. + initializer "active_record.initialize_database" do |app| + ActiveRecord::Base.configurations = app.config.database_configuration + ActiveRecord::Base.establish_connection + end + + initializer "active_record.initialize_timezone" do + ActiveRecord::Base.time_zone_aware_attributes = true + ActiveRecord::Base.default_timezone = :utc + end + + # Setup database middleware after initializers have run + initializer "active_record.initialize_database_middleware" do |app| + middleware = app.config.middleware + if middleware.include?(ActiveRecord::SessionStore) + middleware.insert_before ActiveRecord::SessionStore, ActiveRecord::ConnectionAdapters::ConnectionManagement + middleware.insert_before ActiveRecord::SessionStore, ActiveRecord::QueryCache + else + middleware.use ActiveRecord::ConnectionAdapters::ConnectionManagement + middleware.use ActiveRecord::QueryCache + end + end + + initializer "active_record.load_observers" do + ActiveRecord::Base.instantiate_observers + end + + # TODO: ActiveRecord::Base.logger should delegate to its own config.logger + initializer "active_record.logger" do + ActiveRecord::Base.logger ||= Rails.logger + end + + end +end \ No newline at end of file -- cgit v1.2.3 From d2bd71a145ddc5e3e3750edc9a09eab742aaf02a Mon Sep 17 00:00:00 2001 From: Carlhuda Date: Wed, 23 Dec 2009 17:01:07 -0800 Subject: Finish moving config.frameworks-dependent code to the framework plugin --- activerecord/lib/active_record/notifications.rb | 5 ----- activerecord/lib/active_record/rails.rb | 8 ++++++++ 2 files changed, 8 insertions(+), 5 deletions(-) delete mode 100644 activerecord/lib/active_record/notifications.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/notifications.rb b/activerecord/lib/active_record/notifications.rb deleted file mode 100644 index 562a5b91f4..0000000000 --- a/activerecord/lib/active_record/notifications.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'active_support/notifications' - -ActiveSupport::Notifications.subscribe("sql") do |name, before, after, result, instrumenter_id, payload| - ActiveRecord::Base.connection.log_info(payload[:sql], name, after - before) -end diff --git a/activerecord/lib/active_record/rails.rb b/activerecord/lib/active_record/rails.rb index 4071385563..ddbc555113 100644 --- a/activerecord/lib/active_record/rails.rb +++ b/activerecord/lib/active_record/rails.rb @@ -47,5 +47,13 @@ module ActiveRecord ActiveRecord::Base.logger ||= Rails.logger end + initializer "active_record.notifications" do + require 'active_support/notifications' + + ActiveSupport::Notifications.subscribe("sql") do |name, before, after, result, instrumenter_id, payload| + ActiveRecord::Base.connection.log_info(payload[:sql], name, after - before) + end + end + end end \ No newline at end of file -- cgit v1.2.3 From 2e79ec71a542c2d2e7bedbe12eda0b5e177fb0e0 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 26 Dec 2009 01:31:11 +0530 Subject: Model.scoped now returns a relation if invoked without any arguments Example : posts = Post.scoped posts.size # Fires "select count(*) from posts" and returns the count posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects --- activerecord/lib/active_record/named_scope.rb | 30 +++++++++++++++++---------- 1 file changed, 19 insertions(+), 11 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index bbe2d1f205..321871104c 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -6,18 +6,26 @@ module ActiveRecord module NamedScope extend ActiveSupport::Concern - # All subclasses of ActiveRecord::Base have one named scope: - # * scoped - which allows for the creation of anonymous \scopes, on the fly: Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions) - # - # These anonymous \scopes tend to be useful when procedurally generating complex queries, where passing - # intermediate values (scopes) around as first-class objects is convenient. - # - # You can define a scope that applies to all finders using ActiveRecord::Base.default_scope. - included do - named_scope :scoped, lambda { |scope| scope } - end - module ClassMethods + # Returns a relation if invoked without any arguments. + # + # posts = Post.scoped + # posts.size # Fires "select count(*) from posts" and returns the count + # posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects + # + # Returns an anonymous named scope if any options are supplied. + # + # shirts = Shirt.scoped(:conditions => {:color => 'red'}) + # shirts = shirts.scoped(:include => :washing_instructions) + # + # Anonymous \scopes tend to be useful when procedurally generating complex queries, where passing + # intermediate values (scopes) around as first-class objects is convenient. + # + # You can define a scope that applies to all finders using ActiveRecord::Base.default_scope. + def scoped(options = {}, &block) + options.present? ? Scope.new(self, options, &block) : arel_table + end + def scopes read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {}) end -- cgit v1.2.3 From a7fd564ab197a376336ebfa8bceaf14553e7628f Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 26 Dec 2009 02:53:10 +0530 Subject: Add Model.select/group/order/limit/joins/conditions/preload/eager_load class methods returning a lazy relation. Examples : posts = Post.select('id).order('name') # Returns a lazy relation posts.each {|p| puts p.id } # Fires "select id from posts order by name" --- activerecord/lib/active_record/base.rb | 10 ++--- activerecord/lib/active_record/named_scope.rb | 21 +++++++++- activerecord/lib/active_record/relation.rb | 56 +++++++++++++++------------ 3 files changed, 55 insertions(+), 32 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 321bba466e..03324b7f24 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -13,6 +13,7 @@ require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/string/behavior' require 'active_support/core_ext/object/metaclass' +require 'active_support/core_ext/module/delegation' module ActiveRecord #:nodoc: # Generic Active Record exception class. @@ -650,6 +651,8 @@ module ActiveRecord #:nodoc: end end + delegate :select, :group, :order, :limit, :joins, :conditions, :preload, :eager_load, :to => :arel_table + # A convenience wrapper for find(:first, *args). You can pass in all the # same arguments to this method as you can to find(:first). def first(*args) @@ -1514,13 +1517,8 @@ module ActiveRecord #:nodoc: "(#{segments.join(') AND (')})" unless segments.empty? end - def arel_table(table = nil) - table = table_name if table.blank? - if @arel_table.nil? || @arel_table.name != table - @arel_table = Relation.new(self, Arel::Table.new(table)) - end - @arel_table + Relation.new(self, Arel::Table.new(table || table_name)) end private diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 321871104c..38d54fa8ec 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -23,7 +23,26 @@ module ActiveRecord # # You can define a scope that applies to all finders using ActiveRecord::Base.default_scope. def scoped(options = {}, &block) - options.present? ? Scope.new(self, options, &block) : arel_table + if options.present? + Scope.new(self, options, &block) + else + if !scoped?(:find) + relation = arel_table + else + relation = construct_finder_arel + include_associations = scope(:find, :include) + + if include_associations.present? + if references_eager_loaded_tables?(options) + relation.eager_load(include_associations) + else + relation.preload(include_associations) + end + end + end + + relation + end end def scopes diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 5f0eec754f..28f04e5d85 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -4,20 +4,20 @@ module ActiveRecord delegate :length, :collect, :find, :map, :each, :to => :to_a attr_reader :relation, :klass - def initialize(klass, relation) + def initialize(klass, relation, readonly = false, preload = [], eager_load = []) @klass, @relation = klass, relation - @readonly = false - @associations_to_preload = [] - @eager_load_associations = [] + @readonly = readonly + @associations_to_preload = preload + @eager_load_associations = eager_load end - def preload(association) - @associations_to_preload += association + def preload(associations) + @associations_to_preload << associations self end - def eager_load(association) - @eager_load_associations += association + def eager_load(associations) + @eager_load_associations += Array.wrap(associations) self end @@ -45,7 +45,7 @@ module ActiveRecord @klass.find_by_sql(@relation.to_sql) end - @klass.send(:preload_associations, records, @associations_to_preload) unless @associations_to_preload.empty? + @associations_to_preload.each {|associations| @klass.send(:preload_associations, records, associations) } records.each { |record| record.readonly! } if @readonly records @@ -57,27 +57,27 @@ module ActiveRecord end def select(selects) - selects.blank? ? self : Relation.new(@klass, @relation.project(selects)) + selects.blank? ? self : create_new_relation(@relation.project(selects)) end def group(groups) - groups.blank? ? self : Relation.new(@klass, @relation.group(groups)) + groups.blank? ? self : create_new_relation(@relation.group(groups)) end def order(orders) - orders.blank? ? self : Relation.new(@klass, @relation.order(orders)) + orders.blank? ? self : create_new_relation(@relation.order(orders)) end def limit(limits) - limits.blank? ? self : Relation.new(@klass, @relation.take(limits)) + limits.blank? ? self : create_new_relation(@relation.take(limits)) end def offset(offsets) - offsets.blank? ? self : Relation.new(@klass, @relation.skip(offsets)) + offsets.blank? ? self : create_new_relation(@relation.skip(offsets)) end def on(join) - join.blank? ? self : Relation.new(@klass, @relation.on(join)) + join.blank? ? self : create_new_relation(@relation.on(join)) end def joins(join, join_type = nil) @@ -96,7 +96,7 @@ module ActiveRecord else @relation.join(join, join_type) end - Relation.new(@klass, join) + create_new_relation(join) end end @@ -105,7 +105,7 @@ module ActiveRecord self else conditions = @klass.send(:merge_conditions, conditions) if [String, Hash, Array].include?(conditions.class) - Relation.new(@klass, @relation.where(conditions)) + create_new_relation(@relation.where(conditions)) end end @@ -114,14 +114,20 @@ module ActiveRecord end private - def method_missing(method, *args, &block) - if @relation.respond_to?(method) - @relation.send(method, *args, &block) - elsif Array.method_defined?(method) - to_a.send(method, *args, &block) - else - super - end + + def method_missing(method, *args, &block) + if @relation.respond_to?(method) + @relation.send(method, *args, &block) + elsif Array.method_defined?(method) + to_a.send(method, *args, &block) + else + super end + end + + def create_new_relation(relation) + Relation.new(@klass, relation, @readonly, @associations_to_preload, @eager_load_associations) + end + end end -- cgit v1.2.3 From 95274b28d95ad85ada25eb0c697ce5650ae488a9 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 26 Dec 2009 03:50:57 +0530 Subject: Rename Model.conditions and relation.conditions to .where --- activerecord/lib/active_record/associations.rb | 6 +++--- .../associations/has_and_belongs_to_many_association.rb | 2 +- .../lib/active_record/associations/has_many_association.rb | 2 +- activerecord/lib/active_record/base.rb | 12 ++++++------ activerecord/lib/active_record/calculations.rb | 2 +- activerecord/lib/active_record/relation.rb | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 8dcb3a7711..0ed46046ff 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1717,9 +1717,9 @@ module ActiveRecord select(column_aliases(join_dependency)). group(construct_group(options[:group], options[:having], scope)). order(construct_order(options[:order], scope)). - conditions(construct_conditions(options[:conditions], scope)) + where(construct_conditions(options[:conditions], scope)) - relation = relation.conditions(construct_arel_limited_ids_condition(options, join_dependency)) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit]) + relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency)) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit]) relation = relation.limit(construct_limit(options[:limit], scope)) if using_limitable_reflections?(join_dependency.reflections) relation @@ -1757,7 +1757,7 @@ module ActiveRecord end relation = relation.joins(construct_join(options[:joins], scope)). - conditions(construct_conditions(options[:conditions], scope)). + where(construct_conditions(options[:conditions], scope)). group(construct_group(options[:group], options[:having], scope)). order(construct_order(options[:order], scope)). limit(construct_limit(options[:limit], scope)). diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index b01faa5212..9569b0c6f9 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -71,7 +71,7 @@ module ActiveRecord records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) } else relation = arel_table(@reflection.options[:join_table]) - relation.conditions(relation[@reflection.primary_key_name].eq(@owner.id). + relation.where(relation[@reflection.primary_key_name].eq(@owner.id). and(Arel::Predicates::In.new(relation[@reflection.association_foreign_key], records.map(&:id))) ).delete end diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index cd31b0e211..be74ddfcf0 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -70,7 +70,7 @@ module ActiveRecord @reflection.klass.delete(records.map { |record| record.id }) else relation = arel_table(@reflection.table_name) - relation.conditions(relation[@reflection.primary_key_name].eq(@owner.id). + relation.where(relation[@reflection.primary_key_name].eq(@owner.id). and(Arel::Predicates::In.new(relation[@reflection.klass.primary_key], records.map(&:id))) ).update(relation[@reflection.primary_key_name] => nil) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 03324b7f24..3c41d16f63 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -651,7 +651,7 @@ module ActiveRecord #:nodoc: end end - delegate :select, :group, :order, :limit, :joins, :conditions, :preload, :eager_load, :to => :arel_table + delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :to => :arel_table # A convenience wrapper for find(:first, *args). You can pass in all the # same arguments to this method as you can to find(:first). @@ -885,7 +885,7 @@ module ActiveRecord #:nodoc: relation = arel_table if conditions = construct_conditions(conditions, scope) - relation = relation.conditions(Arel::SqlLiteral.new(conditions)) + relation = relation.where(Arel::SqlLiteral.new(conditions)) end relation = if options.has_key?(:limit) || (scope && scope[:limit]) @@ -948,7 +948,7 @@ module ActiveRecord #:nodoc: # associations or call your before_* or +after_destroy+ callbacks, use the +destroy_all+ method instead. def delete_all(conditions = nil) if conditions - arel_table.conditions(Arel::SqlLiteral.new(construct_conditions(conditions, scope(:find)))).delete + arel_table.where(Arel::SqlLiteral.new(construct_conditions(conditions, scope(:find)))).delete else arel_table.delete end @@ -1689,7 +1689,7 @@ module ActiveRecord #:nodoc: # TODO add lock to Arel relation = arel_table(options[:from]). joins(construct_join(options[:joins], scope)). - conditions(construct_conditions(options[:conditions], scope)). + where(construct_conditions(options[:conditions], scope)). select(options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))). group(construct_group(options[:group], options[:having], scope)). order(construct_order(options[:order], scope)). @@ -2564,7 +2564,7 @@ module ActiveRecord #:nodoc: # be made (since they can't be persisted). def destroy unless new_record? - self.class.arel_table.conditions(self.class.arel_table[self.class.primary_key].eq(id)).delete + self.class.arel_table.where(self.class.arel_table[self.class.primary_key].eq(id)).delete end @destroyed = true @@ -2851,7 +2851,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.arel_table.conditions(self.class.arel_table[self.class.primary_key].eq(id)).update(attributes_with_values) + self.class.arel_table.where(self.class.arel_table[self.class.primary_key].eq(id)).update(attributes_with_values) end # Creates a record with values matching those of the instance attributes diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index 40242333e5..fcba23dc0d 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -148,7 +148,7 @@ module ActiveRecord else relation = arel_table(options[:from]). joins(construct_join(options[:joins], scope)). - conditions(construct_conditions(options[:conditions], scope)). + where(construct_conditions(options[:conditions], scope)). order(options[:order]). limit(options[:limit]). offset(options[:offset]) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 28f04e5d85..6cc2befdf3 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -100,7 +100,7 @@ module ActiveRecord end end - def conditions(conditions) + def where(conditions) if conditions.blank? self else -- cgit v1.2.3 From a73fa9356feb82b739dd734b2b305f1f08f24337 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 26 Dec 2009 04:07:50 +0530 Subject: Stop supporting blank arguments to AR#relation query methods --- activerecord/lib/active_record/relation.rb | 46 ++++++++++++------------------ 1 file changed, 19 insertions(+), 27 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 6cc2befdf3..5c0b8c6f83 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -57,56 +57,48 @@ module ActiveRecord end def select(selects) - selects.blank? ? self : create_new_relation(@relation.project(selects)) + create_new_relation(@relation.project(selects)) end def group(groups) - groups.blank? ? self : create_new_relation(@relation.group(groups)) + create_new_relation(@relation.group(groups)) end def order(orders) - orders.blank? ? self : create_new_relation(@relation.order(orders)) + create_new_relation(@relation.order(orders)) end def limit(limits) - limits.blank? ? self : create_new_relation(@relation.take(limits)) + create_new_relation(@relation.take(limits)) end def offset(offsets) - offsets.blank? ? self : create_new_relation(@relation.skip(offsets)) + create_new_relation(@relation.skip(offsets)) end def on(join) - join.blank? ? self : create_new_relation(@relation.on(join)) + create_new_relation(@relation.on(join)) end def joins(join, join_type = nil) - if join.blank? - self - else - join = case join - when String - @relation.join(join) - when Hash, Array, Symbol - if @klass.send(:array_of_strings?, join) - @relation.join(join.join(' ')) - else - @relation.join(@klass.send(:build_association_joins, join)) - end + join = case join + when String + @relation.join(join) + when Hash, Array, Symbol + if @klass.send(:array_of_strings?, join) + @relation.join(join.join(' ')) else - @relation.join(join, join_type) - end - create_new_relation(join) + @relation.join(@klass.send(:build_association_joins, join)) + end + else + @relation.join(join, join_type) end + create_new_relation(join) end def where(conditions) - if conditions.blank? - self - else - conditions = @klass.send(:merge_conditions, conditions) if [String, Hash, Array].include?(conditions.class) - create_new_relation(@relation.where(conditions)) - end + conditions = @klass.send(:merge_conditions, conditions) if [String, Hash, Array].include?(conditions.class) + create_new_relation(@relation.where(conditions)) end def respond_to?(method) -- cgit v1.2.3 From feb8b20eb552eee678448233672733c6e7ca9571 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 26 Dec 2009 12:39:44 +0530 Subject: Add Relation#all as an alias for to_a --- activerecord/lib/active_record/relation.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 5c0b8c6f83..9aa45fb294 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -51,6 +51,8 @@ module ActiveRecord records end + alias all to_a + def first @relation = @relation.take(1) to_a.first -- cgit v1.2.3 From 187fbe5cbadf9d6fe5009a4a436fac28bcf8c99c Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 26 Dec 2009 12:57:34 +0530 Subject: Add support for multiple arguments to .where finder --- activerecord/lib/active_record/relation.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 9aa45fb294..40ccdd4696 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -98,8 +98,13 @@ module ActiveRecord create_new_relation(join) end - def where(conditions) - conditions = @klass.send(:merge_conditions, conditions) if [String, Hash, Array].include?(conditions.class) + def where(*args) + if [String, Hash, Array].include?(args.first.class) + conditions = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first) + else + conditions = args.first + end + create_new_relation(@relation.where(conditions)) end -- cgit v1.2.3 From 284d186cf4e055cefb2f8482d016e3bd09e9c341 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 26 Dec 2009 14:26:02 +0530 Subject: Make sure the relations are always immutable --- activerecord/lib/active_record/relation.rb | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 40ccdd4696..3dedf44190 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -12,18 +12,15 @@ module ActiveRecord end def preload(associations) - @associations_to_preload << associations - self + create_new_relation(@relation, @readonly, @associations_to_preload + Array.wrap(associations)) end def eager_load(associations) - @eager_load_associations += Array.wrap(associations) - self + create_new_relation(@relation, @readonly, @associations_to_preload, @eager_load_associations + Array.wrap(associations)) end def readonly - @readonly = true - self + create_new_relation(@relation, true) end def to_a @@ -124,8 +121,8 @@ module ActiveRecord end end - def create_new_relation(relation) - Relation.new(@klass, relation, @readonly, @associations_to_preload, @eager_load_associations) + def create_new_relation(relation, readonly = @readonly, preload = @associations_to_preload, eager_load = @eager_load_associations) + Relation.new(@klass, relation, readonly, preload, eager_load) end end -- cgit v1.2.3 From 9d3d60c64a9ce69b9932f5c543112199e576c2ad Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 26 Dec 2009 14:40:45 +0530 Subject: Ensure preload and eager_load finder methods accept multiple arguments --- activerecord/lib/active_record/relation.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 3dedf44190..c02acba786 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -11,11 +11,11 @@ module ActiveRecord @eager_load_associations = eager_load end - def preload(associations) + def preload(*associations) create_new_relation(@relation, @readonly, @associations_to_preload + Array.wrap(associations)) end - def eager_load(associations) + def eager_load(*associations) create_new_relation(@relation, @readonly, @associations_to_preload, @eager_load_associations + Array.wrap(associations)) end -- cgit v1.2.3 From 3c5a7dcaf55f427f3a48e206feb06410d011ca4f Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 26 Dec 2009 15:07:00 +0530 Subject: Cache the loaded relations --- activerecord/lib/active_record/relation.rb | 75 +++++++++++++++++------------- 1 file changed, 43 insertions(+), 32 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index c02acba786..a030ba29fa 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -9,6 +9,7 @@ module ActiveRecord @readonly = readonly @associations_to_preload = preload @eager_load_associations = eager_load + @loaded = false end def preload(*associations) @@ -23,38 +24,6 @@ module ActiveRecord create_new_relation(@relation, true) end - def to_a - records = if @eager_load_associations.any? - catch :invalid_query do - return @klass.send(:find_with_associations, { - :select => @relation.send(:select_clauses).join(', '), - :joins => @relation.joins(relation), - :group => @relation.send(:group_clauses).join(', '), - :order => @relation.send(:order_clauses).join(', '), - :conditions => @relation.send(:where_clauses).join("\n\tAND "), - :limit => @relation.taken, - :offset => @relation.skipped - }, - ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations, nil)) - end - [] - else - @klass.find_by_sql(@relation.to_sql) - end - - @associations_to_preload.each {|associations| @klass.send(:preload_associations, records, associations) } - records.each { |record| record.readonly! } if @readonly - - records - end - - alias all to_a - - def first - @relation = @relation.take(1) - to_a.first - end - def select(selects) create_new_relation(@relation.project(selects)) end @@ -109,6 +78,48 @@ module ActiveRecord @relation.respond_to?(method) || Array.method_defined?(method) || super end + def to_a + return @records if loaded? + + @records = if @eager_load_associations.any? + catch :invalid_query do + return @klass.send(:find_with_associations, { + :select => @relation.send(:select_clauses).join(', '), + :joins => @relation.joins(relation), + :group => @relation.send(:group_clauses).join(', '), + :order => @relation.send(:order_clauses).join(', '), + :conditions => @relation.send(:where_clauses).join("\n\tAND "), + :limit => @relation.taken, + :offset => @relation.skipped + }, + ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations, nil)) + end + [] + else + @klass.find_by_sql(@relation.to_sql) + end + + @associations_to_preload.each {|associations| @klass.send(:preload_associations, @records, associations) } + @records.each { |record| record.readonly! } if @readonly + + @loaded = true + @records + end + + alias all to_a + + def first + if loaded? + @records.first + else + @first ||= limit(1).to_a[0] + end + end + + def loaded? + @loaded + end + private def method_missing(method, *args, &block) -- cgit v1.2.3 From 9a9f97af2815469e6f28dee9b88577251ef1b832 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 26 Dec 2009 15:28:23 +0530 Subject: Add relation.reload to force reloading the records --- activerecord/lib/active_record/relation.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index a030ba29fa..853103a606 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -120,6 +120,12 @@ module ActiveRecord @loaded end + def reload + @loaded = false + @records = @first = nil + self + end + private def method_missing(method, *args, &block) -- cgit v1.2.3 From c6258ee313653bc54e94e0008f1c098ed68aa3f4 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 26 Dec 2009 19:15:05 +0530 Subject: Ensure all the finder methods respect scoping --- activerecord/lib/active_record/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 3c41d16f63..b6d73265a8 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -651,7 +651,7 @@ module ActiveRecord #:nodoc: end end - delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :to => :arel_table + delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :to => :scoped # A convenience wrapper for find(:first, *args). You can pass in all the # same arguments to this method as you can to find(:first). -- cgit v1.2.3 From f3741506981d8d4aafb28066f5a3a0509a4da846 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 27 Dec 2009 00:11:31 +0530 Subject: Ensure Model.scoped adds type conditions for STI models --- activerecord/lib/active_record/named_scope.rb | 1 + activerecord/lib/active_record/relation.rb | 25 ++++++++++++++----------- 2 files changed, 15 insertions(+), 11 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 38d54fa8ec..96d361093f 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -28,6 +28,7 @@ module ActiveRecord else if !scoped?(:find) relation = arel_table + relation = relation.where(type_condition) if finder_needs_type_condition? else relation = construct_finder_arel include_associations = scope(:find, :include) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 853103a606..b6800b07b7 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -49,19 +49,22 @@ module ActiveRecord end def joins(join, join_type = nil) - join = case join - when String - @relation.join(join) - when Hash, Array, Symbol - if @klass.send(:array_of_strings?, join) - @relation.join(join.join(' ')) - else - @relation.join(@klass.send(:build_association_joins, join)) - end + return self if join.blank? + + join_relation = case join + when String + @relation.join(join) + when Hash, Array, Symbol + if @klass.send(:array_of_strings?, join) + @relation.join(join.join(' ')) else - @relation.join(join, join_type) + @relation.join(@klass.send(:build_association_joins, join)) + end + else + @relation.join(join, join_type) end - create_new_relation(join) + + create_new_relation(join_relation) end def where(*args) -- cgit v1.2.3 From 83f24afd44ecd6fb6e38d5e8a6830dfca4bf5ffe Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 27 Dec 2009 00:21:24 +0530 Subject: Add new finder methods to association collection. --- activerecord/lib/active_record/associations.rb | 4 ++-- .../associations/association_collection.rb | 19 +++++++++++++++++-- .../lib/active_record/autosave_association.rb | 4 ++-- 3 files changed, 21 insertions(+), 6 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 0ed46046ff..2735bc5141 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1382,9 +1382,9 @@ module ActiveRecord if reflection.through_reflection && reflection.source_reflection.belongs_to? through = reflection.through_reflection primary_key = reflection.source_reflection.primary_key_name - send(through.name).all(:select => "DISTINCT #{through.quoted_table_name}.#{primary_key}").map!(&:"#{primary_key}") + send(through.name).select("DISTINCT #{through.quoted_table_name}.#{primary_key}").map!(&:"#{primary_key}") else - send(reflection.name).all(:select => "#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map!(&:id) + send(reflection.name).select("#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map!(&:id) end end end diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 25e329c0c1..b85d76e8d3 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -20,7 +20,22 @@ module ActiveRecord super construct_sql end - + + delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :to => :scoped + + def select(select = nil, &block) + if block_given? + load_target + @target.select(&block) + else + scoped.select(select) + end + end + + def scoped + with_scope(construct_scope) { @reflection.klass.scoped } + end + def find(*args) options = args.extract_options! @@ -383,7 +398,7 @@ module ActiveRecord loaded if target target end - + def method_missing(method, *args) if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method)) if block_given? diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 8f37fcd515..c0d8904bc8 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -221,9 +221,9 @@ module ActiveRecord if new_record association elsif association.loaded? - autosave ? association : association.select { |record| record.new_record? } + autosave ? association : association.find_all { |record| record.new_record? } else - autosave ? association.target : association.target.select { |record| record.new_record? } + autosave ? association.target : association.target.find_all { |record| record.new_record? } end end -- cgit v1.2.3 From 75ba102a80965b2612df0253d1278581a88b8d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 26 Dec 2009 20:28:53 +0100 Subject: Remove ActionView inline logging to ActiveSupport::Notifications and create ActionController::Base#log_event, so everything can be logged within one listener. Also expose log_process_action as a hook for different modules to include their own information during the action processing. This allow ActiveRecord to hook and any other ORM. Finally, this commit changes 'Processing' and 'Rendering' in logs to 'Processed' and 'Rendered' because at the point it's logged, everying already happened. --- activerecord/lib/active_record/rails.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/rails.rb b/activerecord/lib/active_record/rails.rb index ddbc555113..e272d4eebc 100644 --- a/activerecord/lib/active_record/rails.rb +++ b/activerecord/lib/active_record/rails.rb @@ -50,8 +50,8 @@ module ActiveRecord initializer "active_record.notifications" do require 'active_support/notifications' - ActiveSupport::Notifications.subscribe("sql") do |name, before, after, result, instrumenter_id, payload| - ActiveRecord::Base.connection.log_info(payload[:sql], name, after - before) + ActiveSupport::Notifications.subscribe("sql") do |name, before, after, instrumenter_id, payload| + ActiveRecord::Base.connection.log_info(payload[:sql], payload[:name], (after - before) * 1000) end end -- cgit v1.2.3 From f6f416c58e805604390314e2e8e5ecf6a0a78b4f Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 27 Dec 2009 03:25:29 +0530 Subject: Add find_by_* and find_all_by_* finders to ActiveRecord::Relation --- activerecord/lib/active_record/relation.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index b6800b07b7..f9ca6cbbd2 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -136,6 +136,20 @@ module ActiveRecord @relation.send(method, *args, &block) elsif Array.method_defined?(method) to_a.send(method, *args, &block) + elsif match = DynamicFinderMatch.match(method) + attributes = match.attribute_names + super unless @klass.send(:all_attributes_exists?, attributes) + + if match.finder? + conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h} + result = where(conditions).send(match.finder) + + if match.bang? && result.blank? + raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}" + else + result + end + end else super end -- cgit v1.2.3 From 8829d6ecc6b3e1a36a41decaf8237f6024be2c06 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 27 Dec 2009 13:15:00 +0530 Subject: Make Model.find_by_* and Model.find_all_by_* use relations and remove dynamic method caching --- activerecord/lib/active_record/base.rb | 76 ++++++++------------------- activerecord/lib/active_record/named_scope.rb | 18 ++----- activerecord/lib/active_record/relation.rb | 22 ++++---- 3 files changed, 38 insertions(+), 78 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index b6d73265a8..31737043eb 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -671,20 +671,10 @@ module ActiveRecord #:nodoc: options = args.extract_options! if options.empty? && !scoped?(:find) - relation = arel_table + arel_table else - relation = construct_finder_arel(options) - include_associations = merge_includes(scope(:find, :include), options[:include]) - - if include_associations.any? - if references_eager_loaded_tables?(options) - relation.eager_load(include_associations) - else - relation.preload(include_associations) - end - end + construct_finder_arel_with_includes(options) end - relation end # Executes a custom SQL query against your database and returns all the results. The results will @@ -1687,6 +1677,8 @@ module ActiveRecord #:nodoc: def construct_finder_arel(options = {}, scope = scope(:find)) # TODO add lock to Arel + validate_find_options(options) + relation = arel_table(options[:from]). joins(construct_join(options[:joins], scope)). where(construct_conditions(options[:conditions], scope)). @@ -1701,6 +1693,21 @@ module ActiveRecord #:nodoc: relation end + def construct_finder_arel_with_includes(options = {}) + relation = construct_finder_arel(options) + include_associations = merge_includes(scope(:find, :include), options[:include]) + + if include_associations.any? + if references_eager_loaded_tables?(options) + relation = relation.eager_load(include_associations) + else + relation = relation.preload(include_associations) + end + end + + relation + end + def construct_finder_sql(options, scope = scope(:find)) construct_finder_arel(options, scope).to_sql end @@ -1853,48 +1860,9 @@ module ActiveRecord #:nodoc: attribute_names = match.attribute_names super unless all_attributes_exists?(attribute_names) if match.finder? - finder = match.finder - bang = match.bang? - # def self.find_by_login_and_activated(*args) - # options = args.extract_options! - # attributes = construct_attributes_from_arguments( - # [:login,:activated], - # args - # ) - # finder_options = { :conditions => attributes } - # validate_find_options(options) - # set_readonly_option!(options) - # - # if options[:conditions] - # with_scope(:find => finder_options) do - # find(:first, options) - # end - # else - # find(:first, options.merge(finder_options)) - # end - # end - self.class_eval %{ - def self.#{method_id}(*args) - options = args.extract_options! - attributes = construct_attributes_from_arguments( - [:#{attribute_names.join(',:')}], - args - ) - finder_options = { :conditions => attributes } - validate_find_options(options) - set_readonly_option!(options) - - #{'result = ' if bang}if options[:conditions] - with_scope(:find => finder_options) do - find(:#{finder}, options) - end - else - find(:#{finder}, options.merge(finder_options)) - end - #{'result || raise(RecordNotFound, "Couldn\'t find #{name} with #{attributes.to_a.collect { |pair| pair.join(\' = \') }.join(\', \')}")' if bang} - end - }, __FILE__, __LINE__ - send(method_id, *arguments) + options = arguments.extract_options! + relation = options.any? ? construct_finder_arel_with_includes(options) : scoped + relation.send :find_by_attributes, match, attribute_names, *arguments elsif match.instantiator? instantiator = match.instantiator # def self.find_or_create_by_user_id(*args) diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 96d361093f..a6336e762a 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -26,23 +26,11 @@ module ActiveRecord if options.present? Scope.new(self, options, &block) else - if !scoped?(:find) - relation = arel_table - relation = relation.where(type_condition) if finder_needs_type_condition? + unless scoped?(:find) + finder_needs_type_condition? ? arel_table.where(type_condition) : arel_table else - relation = construct_finder_arel - include_associations = scope(:find, :include) - - if include_associations.present? - if references_eager_loaded_tables?(options) - relation.eager_load(include_associations) - else - relation.preload(include_associations) - end - end + construct_finder_arel_with_includes end - - relation end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index f9ca6cbbd2..bcae352cc6 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -129,7 +129,7 @@ module ActiveRecord self end - private + protected def method_missing(method, *args, &block) if @relation.respond_to?(method) @@ -141,20 +141,24 @@ module ActiveRecord super unless @klass.send(:all_attributes_exists?, attributes) if match.finder? - conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h} - result = where(conditions).send(match.finder) - - if match.bang? && result.blank? - raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}" - else - result - end + find_by_attributes(match, attributes, *args) end else super end end + def find_by_attributes(match, attributes, *args) + conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h} + result = where(conditions).send(match.finder) + + if match.bang? && result.blank? + raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}" + else + result + end + end + def create_new_relation(relation, readonly = @readonly, preload = @associations_to_preload, eager_load = @eager_load_associations) Relation.new(@klass, relation, readonly, preload, eager_load) end -- cgit v1.2.3 From d511de0261003aae4913e3c24f76df6e03a35916 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 27 Dec 2009 14:28:19 +0530 Subject: Add find_or_create_by_* and find_or_initialize_by_* to relations --- activerecord/lib/active_record/relation.rb | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index bcae352cc6..f17b889c53 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -142,6 +142,8 @@ module ActiveRecord if match.finder? find_by_attributes(match, attributes, *args) + elsif match.instantiator? + find_or_instantiator_by_attributes(match, attributes, *args, &block) end else super @@ -159,6 +161,27 @@ module ActiveRecord end end + def find_or_instantiator_by_attributes(match, attributes, *args) + guard_protected_attributes = false + + attributes_for_create = conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h} + + if args[0].is_a?(Hash) + guard_protected_attributes = true + conditions = args[0].with_indifferent_access.slice(*attributes).symbolize_keys + end + + record = where(conditions).first + + unless record + record = @klass.new { |r| r.send(:attributes=, attributes_for_create, guard_protected_attributes) } + yield(record) if block_given? + record.save if match.instantiator == :create + end + + record + end + def create_new_relation(relation, readonly = @readonly, preload = @associations_to_preload, eager_load = @eager_load_associations) Relation.new(@klass, relation, readonly, preload, eager_load) end -- cgit v1.2.3 From 85770ec7139fcba985310d239d4c57cfe6f6c60b Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 27 Dec 2009 14:46:38 +0530 Subject: Make Model.find_or_create_by_* and find_or_initialize_by_* use relations and remove method caching --- activerecord/lib/active_record/base.rb | 55 +----------------------------- activerecord/lib/active_record/relation.rb | 7 ++-- 2 files changed, 5 insertions(+), 57 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 31737043eb..cba1e0ebe6 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1864,60 +1864,7 @@ module ActiveRecord #:nodoc: relation = options.any? ? construct_finder_arel_with_includes(options) : scoped relation.send :find_by_attributes, match, attribute_names, *arguments elsif match.instantiator? - instantiator = match.instantiator - # def self.find_or_create_by_user_id(*args) - # guard_protected_attributes = false - # - # if args[0].is_a?(Hash) - # guard_protected_attributes = true - # attributes = args[0].with_indifferent_access - # find_attributes = attributes.slice(*[:user_id]) - # else - # find_attributes = attributes = construct_attributes_from_arguments([:user_id], args) - # end - # - # options = { :conditions => find_attributes } - # set_readonly_option!(options) - # - # record = find(:first, options) - # - # if record.nil? - # record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) } - # yield(record) if block_given? - # record.save - # record - # else - # record - # end - # end - self.class_eval %{ - def self.#{method_id}(*args) - guard_protected_attributes = false - - if args[0].is_a?(Hash) - guard_protected_attributes = true - attributes = args[0].with_indifferent_access - find_attributes = attributes.slice(*[:#{attribute_names.join(',:')}]) - else - find_attributes = attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args) - end - - options = { :conditions => find_attributes } - set_readonly_option!(options) - - record = find(:first, options) - - if record.nil? - record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) } - #{'yield(record) if block_given?'} - #{'record.save' if instantiator == :create} - record - else - record - end - end - }, __FILE__, __LINE__ - send(method_id, *arguments, &block) + scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block end elsif match = DynamicScopeMatch.match(method_id) attribute_names = match.attribute_names diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index f17b889c53..89b9b9b4ff 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -164,11 +164,12 @@ module ActiveRecord def find_or_instantiator_by_attributes(match, attributes, *args) guard_protected_attributes = false - attributes_for_create = conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h} - if args[0].is_a?(Hash) guard_protected_attributes = true - conditions = args[0].with_indifferent_access.slice(*attributes).symbolize_keys + attributes_for_create = args[0].with_indifferent_access + conditions = attributes_for_create.slice(*attributes).symbolize_keys + else + attributes_for_create = conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h} end record = where(conditions).first -- cgit v1.2.3 From 1efc8edb5f8799c48ba1992e07a8566bb12b7224 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 27 Dec 2009 14:50:33 +0530 Subject: Fix dynamic finder docs --- activerecord/lib/active_record/base.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index cba1e0ebe6..e619e41329 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1842,9 +1842,8 @@ module ActiveRecord #:nodoc: end # Enables dynamic finders like find_by_user_name(user_name) and find_by_user_name_and_password(user_name, password) - # that are turned into find(:first, :conditions => ["user_name = ?", user_name]) and - # find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password]) respectively. Also works for - # find(:all) by using find_all_by_amount(50) that is turned into find(:all, :conditions => ["amount = ?", 50]). + # that are turned into where(:user_name => user_name).first and where(:user_name => user_name, :password => :password).first + # respectively. Also works for all by using find_all_by_amount(50) that is turned into where(:amount => 50).all. # # It's even possible to use all the additional parameters to +find+. For example, the full interface for +find_all_by_amount+ # is actually find_all_by_amount(amount, options). -- cgit v1.2.3 From 81608cf8fa61973d65f24efbc4ae4931016888e7 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 27 Dec 2009 15:06:45 +0530 Subject: Make Model.all return an array rather than a relation for consistency. Use Model.scoped to get a relation --- activerecord/lib/active_record/base.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index e619e41329..2008dea5e9 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -671,9 +671,9 @@ module ActiveRecord #:nodoc: options = args.extract_options! if options.empty? && !scoped?(:find) - arel_table + arel_table.to_a else - construct_finder_arel_with_includes(options) + construct_finder_arel_with_includes(options).to_a end end -- cgit v1.2.3 From d92c4a84023bc0c8dd75869c9b4d5e50277f4650 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 27 Dec 2009 16:14:04 +0530 Subject: Add find(ids) to relations --- activerecord/lib/active_record/relation.rb | 63 +++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 89b9b9b4ff..c927270eee 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -91,7 +91,7 @@ module ActiveRecord :joins => @relation.joins(relation), :group => @relation.send(:group_clauses).join(', '), :order => @relation.send(:order_clauses).join(', '), - :conditions => @relation.send(:where_clauses).join("\n\tAND "), + :conditions => where_clause, :limit => @relation.taken, :offset => @relation.skipped }, @@ -111,6 +111,25 @@ module ActiveRecord alias all to_a + def find(*ids, &block) + return to_a.find(&block) if block_given? + + expects_array = ids.first.kind_of?(Array) + return ids.first if expects_array && ids.first.empty? + + ids = ids.flatten.compact.uniq + + case ids.size + when 0 + raise RecordNotFound, "Couldn't find #{@klass.name} without an ID" + when 1 + result = find_one(ids.first) + expects_array ? [ result ] : result + else + find_some(ids) + end + end + def first if loaded? @records.first @@ -183,9 +202,51 @@ module ActiveRecord record end + def find_one(id) + record = where(@klass.primary_key => id).first + + unless record + conditions = where_clause(', ') + conditions = " [WHERE #{conditions}]" if conditions.present? + raise RecordNotFound, "Couldn't find #{@klass.name} with ID=#{id}#{conditions}" + end + + record + end + + def find_some(ids) + result = where(@klass.primary_key => ids).all + + expected_size = + if @relation.taken && ids.size > @relation.taken + @relation.taken + else + ids.size + end + + # 11 ids with limit 3, offset 9 should give 2 results. + if @relation.skipped && (ids.size - @relation.skipped < expected_size) + expected_size = ids.size - @relation.skipped + end + + if result.size == expected_size + result + else + conditions = where_clause(', ') + conditions = " [WHERE #{conditions}]" if conditions.present? + + error = "Couldn't find all #{@klass.name.pluralize} with IDs " + error << "(#{ids.join(", ")})#{conditions} (found #{result.size} results, but was looking for #{expected_size})" + raise RecordNotFound, error + end + end + def create_new_relation(relation, readonly = @readonly, preload = @associations_to_preload, eager_load = @eager_load_associations) Relation.new(@klass, relation, readonly, preload, eager_load) end + def where_clause(join_string = "\n\tAND ") + @relation.send(:where_clauses).join(join_string) + end end end -- cgit v1.2.3 From 7b5d0e8b784a60927e923f2c21ab6a7c4f35f5c3 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 27 Dec 2009 16:56:10 +0530 Subject: Make Model.find(ids) use relations --- activerecord/lib/active_record/base.rb | 74 +++++----------------------------- 1 file changed, 11 insertions(+), 63 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 2008dea5e9..302c9fb724 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -644,10 +644,14 @@ module ActiveRecord #:nodoc: set_readonly_option!(options) case args.first - when :first then find_initial(options) - when :last then find_last(options) - when :all then find_every(options) - else find_from_ids(args, options) + when :first + find_initial(options) + when :last + find_last(options) + when :all + find_every(options) + else + construct_finder_arel_with_includes(options).find(*args) end end @@ -1568,64 +1572,6 @@ module ActiveRecord #:nodoc: records end - def find_from_ids(ids, options) - expects_array = ids.first.kind_of?(Array) - return ids.first if expects_array && ids.first.empty? - - ids = ids.flatten.compact.uniq - - case ids.size - when 0 - raise RecordNotFound, "Couldn't find #{name} without an ID" - when 1 - result = find_one(ids.first, options) - expects_array ? [ result ] : result - else - find_some(ids, options) - end - end - - def find_one(id, options) - conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions] - options.update :conditions => "#{quoted_table_name}.#{connection.quote_column_name(primary_key)} = #{quote_value(id,columns_hash[primary_key])}#{conditions}" - - # Use find_every(options).first since the primary key condition - # already ensures we have a single record. Using find_initial adds - # a superfluous :limit => 1. - if result = find_every(options).first - result - else - raise RecordNotFound, "Couldn't find #{name} with ID=#{id}#{conditions}" - end - end - - def find_some(ids, options) - conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions] - ids_list = ids.map { |id| quote_value(id,columns_hash[primary_key]) }.join(',') - options.update :conditions => "#{quoted_table_name}.#{connection.quote_column_name(primary_key)} IN (#{ids_list})#{conditions}" - - result = find_every(options) - - # Determine expected size from limit and offset, not just ids.size. - expected_size = - if options[:limit] && ids.size > options[:limit] - options[:limit] - else - ids.size - end - - # 11 ids with limit 3, offset 9 should give 2 results. - if options[:offset] && (ids.size - options[:offset] < expected_size) - expected_size = ids.size - options[:offset] - end - - if result.size == expected_size - result - else - raise RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids_list})#{conditions} (found #{result.size} results, but was looking for #{expected_size})" - end - end - # Finder methods must instantiate through this method to work with the # single-table inheritance model that makes it possible to create # objects of different types from the same table. @@ -1742,6 +1688,7 @@ module ActiveRecord #:nodoc: def construct_order(order, scope) orders = [] + scoped_order = scope[:order] if scope if order orders << order @@ -1749,7 +1696,8 @@ module ActiveRecord #:nodoc: elsif scoped_order orders << scoped_order end - orders + + orders.reject {|o| o.blank?} end def construct_limit(limit, scope) -- cgit v1.2.3 From 6f5e3a04d655e09c0a9a32fc94c752f84c7011f6 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 27 Dec 2009 17:16:20 +0530 Subject: Relation should supply :from to find_with_associations --- activerecord/lib/active_record/relation.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index c927270eee..f1d1fa0790 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -93,7 +93,8 @@ module ActiveRecord :order => @relation.send(:order_clauses).join(', '), :conditions => where_clause, :limit => @relation.taken, - :offset => @relation.skipped + :offset => @relation.skipped, + :from => @relation.send(:table_sql, Arel::Sql::TableReference.new(@relation)) }, ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations, nil)) end -- cgit v1.2.3 From a3f3fab7955a0874bd58d1d997e344bdde710628 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 27 Dec 2009 17:37:36 +0530 Subject: Add relation.from as a temporary workaround until arel relation has .from option --- activerecord/lib/active_record/base.rb | 3 ++- activerecord/lib/active_record/relation.rb | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 302c9fb724..c0b8e7e14f 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1632,7 +1632,8 @@ module ActiveRecord #:nodoc: group(construct_group(options[:group], options[:having], scope)). order(construct_order(options[:order], scope)). limit(construct_limit(options[:limit], scope)). - offset(construct_offset(options[:offset], scope)) + offset(construct_offset(options[:offset], scope)). + from(options[:from]) relation = relation.readonly if options[:readonly] diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index f1d1fa0790..87f6aa3643 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -28,6 +28,14 @@ module ActiveRecord create_new_relation(@relation.project(selects)) end + # TODO : This is temporary. We need .from in Arel. + attr_writer :from + def from(from) + relation = create_new_relation + relation.from = from + relation + end + def group(groups) create_new_relation(@relation.group(groups)) end @@ -94,7 +102,7 @@ module ActiveRecord :conditions => where_clause, :limit => @relation.taken, :offset => @relation.skipped, - :from => @relation.send(:table_sql, Arel::Sql::TableReference.new(@relation)) + :from => @from }, ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations, nil)) end @@ -242,8 +250,10 @@ module ActiveRecord end end - def create_new_relation(relation, readonly = @readonly, preload = @associations_to_preload, eager_load = @eager_load_associations) - Relation.new(@klass, relation, readonly, preload, eager_load) + def create_new_relation(relation = @relation, readonly = @readonly, preload = @associations_to_preload, eager_load = @eager_load_associations) + r = self.class.new(@klass, relation, readonly, preload, eager_load) + r.from = @from + r end def where_clause(join_string = "\n\tAND ") -- cgit v1.2.3 From b31233485b2ec7be96279448f77c860b0e94279b Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 27 Dec 2009 17:43:31 +0530 Subject: Make Model.find(:all) use relations --- activerecord/lib/active_record/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index c0b8e7e14f..73bd407f8f 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -649,7 +649,7 @@ module ActiveRecord #:nodoc: when :last find_last(options) when :all - find_every(options) + construct_finder_arel_with_includes(options).all else construct_finder_arel_with_includes(options).find(*args) end -- cgit v1.2.3 From 2c8f83556bf3ec9861315e11bc070753ef6bd97c Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 27 Dec 2009 18:00:49 +0530 Subject: Add relation.exists? --- activerecord/lib/active_record/relation.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 87f6aa3643..4ca6871d0f 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -139,6 +139,12 @@ module ActiveRecord end end + def exists?(id = nil) + relation = select("#{@klass.quoted_table_name}.#{@klass.primary_key}").limit(1) + relation = relation.where(@klass.primary_key => id) if id + relation.first ? true : false + end + def first if loaded? @records.first -- cgit v1.2.3 From 97db79ab3c0af7b6805dcaee99384d96ccb3567d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 27 Dec 2009 13:32:40 +0100 Subject: Remove ActiveRecord runtime logging from ActionPack and place in ActiveRecord, adding it through config.action_controller.include hook. --- activerecord/lib/active_record.rb | 1 + .../lib/active_record/controller_runtime.rb | 27 ++++++++++++++++++++++ activerecord/lib/active_record/rails.rb | 3 +++ 3 files changed, 31 insertions(+) create mode 100644 activerecord/lib/active_record/controller_runtime.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 196b87c0ac..2cfd528f2c 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -52,6 +52,7 @@ module ActiveRecord autoload :Batches autoload :Calculations autoload :Callbacks + autoload :ControllerRuntime autoload :DynamicFinderMatch autoload :DynamicScopeMatch autoload :Migration diff --git a/activerecord/lib/active_record/controller_runtime.rb b/activerecord/lib/active_record/controller_runtime.rb new file mode 100644 index 0000000000..1281901ae8 --- /dev/null +++ b/activerecord/lib/active_record/controller_runtime.rb @@ -0,0 +1,27 @@ +module ActiveRecord + module ControllerRuntime + extend ActiveSupport::Concern + + attr_internal :db_runtime + + def cleanup_view_runtime + if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected? + db_rt_before_render = ActiveRecord::Base.connection.reset_runtime + runtime = super + db_rt_after_render = ActiveRecord::Base.connection.reset_runtime + self.db_runtime = db_rt_before_render + db_rt_after_render + runtime - db_rt_after_render + else + super + end + end + + module ClassMethods + def process_log_action(controller) + super + db_runtime = controller.send :db_runtime + logger.info(" ActiveRecord runtime: %.1fms" % db_runtime.to_f) if db_runtime + end + end + end +end \ No newline at end of file diff --git a/activerecord/lib/active_record/rails.rb b/activerecord/lib/active_record/rails.rb index e272d4eebc..8b22f869bc 100644 --- a/activerecord/lib/active_record/rails.rb +++ b/activerecord/lib/active_record/rails.rb @@ -7,6 +7,9 @@ require "action_controller/rails" module ActiveRecord class Plugin < Rails::Plugin plugin_name :active_record + include_modules_in "ActiveRecord::Base" + + config.action_controller.include "ActiveRecord::ControllerRuntime" initializer "active_record.set_configs" do |app| app.config.active_record.each do |k,v| -- cgit v1.2.3 From 59cf5e7bf293f30ef4984842a1bce0d63870fc69 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 27 Dec 2009 18:10:26 +0530 Subject: Make Model.exists? use relation.exists? --- activerecord/lib/active_record/base.rb | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 73bd407f8f..5ea1bc983e 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -732,10 +732,13 @@ module ActiveRecord #:nodoc: # Person.exists?(:name => "David") # Person.exists?(['name LIKE ?', "%#{query}%"]) # Person.exists? - def exists?(id_or_conditions = {}) - find_initial( - :select => "#{quoted_table_name}.#{primary_key}", - :conditions => expand_id_conditions(id_or_conditions)) ? true : false + 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. @@ -1874,14 +1877,6 @@ module ActiveRecord #:nodoc: end end - # Interpret Array and Hash as conditions and anything else as an id. - def expand_id_conditions(id_or_conditions) - case id_or_conditions - when Array, Hash then id_or_conditions - else sanitize_sql(primary_key => id_or_conditions) - end - end - protected # Scope parameters to method calls within the block. Takes a hash of method_name => parameters hash. # method_name may be :find or :create. :find parameters may include the :conditions, :joins, -- cgit v1.2.3 From d6d0fe8c8f68529404d8186634f1f7b5f6f4b32c Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 27 Dec 2009 18:28:59 +0530 Subject: Make Model.find(:first, ..) use relations --- activerecord/lib/active_record/base.rb | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 5ea1bc983e..0766dfe752 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -645,7 +645,7 @@ module ActiveRecord #:nodoc: case args.first when :first - find_initial(options) + construct_finder_arel_with_includes(options).first when :last find_last(options) when :all @@ -1519,11 +1519,6 @@ module ActiveRecord #:nodoc: end private - def find_initial(options) - options.update(:limit => 1) - find_every(options).first - end - def find_last(options) order = options[:order] @@ -1540,7 +1535,7 @@ module ActiveRecord #:nodoc: end begin - find_initial(options.merge({ :order => order })) + construct_finder_arel_with_includes(options).order(order).first ensure scope[:order] = original_scoped_order if original_scoped_order end @@ -1558,23 +1553,6 @@ module ActiveRecord #:nodoc: }.join(',') end - def find_every(options) - include_associations = merge_includes(scope(:find, :include), options[:include]) - - if include_associations.any? && references_eager_loaded_tables?(options) - records = find_with_associations(options) - else - records = find_by_sql(construct_finder_sql(options)) - if include_associations.any? - preload_associations(records, include_associations) - end - end - - records.each { |record| record.readonly! } if options[:readonly] - - records - end - # Finder methods must instantiate through this method to work with the # single-table inheritance model that makes it possible to create # objects of different types from the same table. -- cgit v1.2.3 From 8957f5db5d543b465af2ec55339628476ed17dc2 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 27 Dec 2009 18:32:10 +0530 Subject: Make Model.all just a wrapper for find(:all) --- activerecord/lib/active_record/base.rb | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 0766dfe752..ed031088a7 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -669,16 +669,10 @@ module ActiveRecord #:nodoc: find(:last, *args) end - # Returns an ActiveRecord::Relation object. You can pass in all the same arguments to this method as you can - # to find(:all). + # A convenience wrapper for find(:all, *args). You can pass in all the + # same arguments to this method as you can to find(:all). def all(*args) - options = args.extract_options! - - if options.empty? && !scoped?(:find) - arel_table.to_a - else - construct_finder_arel_with_includes(options).to_a - end + find(:all, *args) end # Executes a custom SQL query against your database and returns all the results. The results will -- cgit v1.2.3 From d5e98dc859e24e9ebf8206a7955c6ac40819a117 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 27 Dec 2009 19:04:30 +0530 Subject: Add relation.last and relation.reverse_order --- activerecord/lib/active_record/relation.rb | 35 +++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 4ca6871d0f..e52c7c3dc6 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -44,6 +44,18 @@ module ActiveRecord create_new_relation(@relation.order(orders)) end + def reverse_order + relation = create_new_relation + relation.instance_variable_set(:@orders, nil) + + order_clause = @relation.send(:order_clauses).join(', ') + if order_clause.present? + relation.order(reverse_sql_order(order_clause)) + else + relation.order("#{@klass.table_name}.#{@klass.primary_key} DESC") + end + end + def limit(limits) create_new_relation(@relation.take(limits)) end @@ -153,13 +165,21 @@ module ActiveRecord end end + def last + if loaded? + @records.last + else + @last ||= reverse_order.limit(1).to_a[0] + end + end + def loaded? @loaded end def reload @loaded = false - @records = @first = nil + @records = @first = @last = nil self end @@ -265,5 +285,18 @@ module ActiveRecord def where_clause(join_string = "\n\tAND ") @relation.send(:where_clauses).join(join_string) end + + def reverse_sql_order(order_query) + order_query.to_s.split(/,/).each { |s| + if s.match(/\s(asc|ASC)$/) + s.gsub!(/\s(asc|ASC)$/, ' DESC') + elsif s.match(/\s(desc|DESC)$/) + s.gsub!(/\s(desc|DESC)$/, ' ASC') + else + s.concat(' DESC') + end + }.join(',') + end + end end -- cgit v1.2.3 From 352cc7c94a19f596b3ecb5f1a2b9712b1519e978 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 27 Dec 2009 19:12:50 +0530 Subject: Make Model.find(:last) use relations --- activerecord/lib/active_record/base.rb | 47 ++++------------------------------ 1 file changed, 5 insertions(+), 42 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index ed031088a7..0d3ae1ef3e 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -640,18 +640,15 @@ module ActiveRecord #:nodoc: # end def find(*args) options = args.extract_options! - validate_find_options(options) set_readonly_option!(options) + relation = construct_finder_arel_with_includes(options) + case args.first - when :first - construct_finder_arel_with_includes(options).first - when :last - find_last(options) - when :all - construct_finder_arel_with_includes(options).all + when :first, :last, :all + relation.send(args.first) else - construct_finder_arel_with_includes(options).find(*args) + relation.find(*args) end end @@ -1513,40 +1510,6 @@ module ActiveRecord #:nodoc: end private - def find_last(options) - order = options[:order] - - if order - order = reverse_sql_order(order) - elsif !scoped?(:find, :order) - order = "#{table_name}.#{primary_key} DESC" - end - - if scoped?(:find, :order) - scope = scope(:find) - original_scoped_order = scope[:order] - scope[:order] = reverse_sql_order(original_scoped_order) - end - - begin - construct_finder_arel_with_includes(options).order(order).first - ensure - scope[:order] = original_scoped_order if original_scoped_order - end - end - - def reverse_sql_order(order_query) - order_query.to_s.split(/,/).each { |s| - if s.match(/\s(asc|ASC)$/) - s.gsub!(/\s(asc|ASC)$/, ' DESC') - elsif s.match(/\s(desc|DESC)$/) - s.gsub!(/\s(desc|DESC)$/, ' ASC') - else - s.concat(' DESC') - end - }.join(',') - end - # Finder methods must instantiate through this method to work with the # single-table inheritance model that makes it possible to create # objects of different types from the same table. -- cgit v1.2.3 From 5cd8818258cf7ce2c90667076d3f61f1c7ed548e Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 27 Dec 2009 19:22:18 +0530 Subject: Make Model.destroy_all use new finders --- activerecord/lib/active_record/base.rb | 2 +- activerecord/lib/active_record/relation.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 0d3ae1ef3e..a3b123c463 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -914,7 +914,7 @@ module ActiveRecord #:nodoc: # Person.destroy_all("last_login < '2004-04-04'") # Person.destroy_all(:status => "inactive") def destroy_all(conditions = nil) - find(:all, :conditions => conditions).each { |object| object.destroy } + where(conditions).each {|object| object.destroy } end # Deletes the records matching +conditions+ without instantiating the records first, and hence not diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index e52c7c3dc6..bec9a88297 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -88,6 +88,8 @@ module ActiveRecord end def where(*args) + return create_new_relation if args.blank? + if [String, Hash, Array].include?(args.first.class) conditions = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first) else -- cgit v1.2.3 From bbdeaae2cac37882b091474ec596ade3de3b5e7c Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 27 Dec 2009 19:35:55 +0530 Subject: Add relation.destroy_all --- activerecord/lib/active_record/base.rb | 2 +- activerecord/lib/active_record/relation.rb | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index a3b123c463..cad75f80e5 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -914,7 +914,7 @@ module ActiveRecord #:nodoc: # Person.destroy_all("last_login < '2004-04-04'") # Person.destroy_all(:status => "inactive") def destroy_all(conditions = nil) - where(conditions).each {|object| object.destroy } + where(conditions).destroy_all end # Deletes the records matching +conditions+ without instantiating the records first, and hence not diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index bec9a88297..4aa460a601 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -175,13 +175,23 @@ module ActiveRecord end end + def destroy_all + to_a.each {|object| object.destroy} + reset + end + def loaded? @loaded end def reload @loaded = false - @records = @first = @last = nil + reset + end + + def reset + @first = @last = nil + @records = [] self end -- cgit v1.2.3 From 5565bab994af1e54f34df5891c635590d22feea0 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 27 Dec 2009 22:32:20 +0530 Subject: Rewrite AssociationCollection#find using relations --- .../associations/association_collection.rb | 32 +++++++++------------- 1 file changed, 13 insertions(+), 19 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index b85d76e8d3..ac970f02b1 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -52,27 +52,21 @@ module ActiveRecord load_target.select { |r| ids.include?(r.id) } end else - conditions = "#{@finder_sql}" - if sanitized_conditions = sanitize_sql(options[:conditions]) - conditions << " AND (#{sanitized_conditions})" - end - - options[:conditions] = conditions + merge_options_from_reflection!(options) + construct_find_options!(options) + + find_scope = construct_scope[:find].slice(:conditions, :order) + + with_scope(:find => find_scope) do + relation = @reflection.klass.send(:construct_finder_arel_with_includes, options) - if options[:order] && @reflection.options[:order] - options[:order] = "#{options[:order]}, #{@reflection.options[:order]}" - elsif @reflection.options[:order] - options[:order] = @reflection.options[:order] + case args.first + when :first, :last, :all + relation.send(args.first) + else + relation.find(*args) + end end - - # Build options specific to association - construct_find_options!(options) - - merge_options_from_reflection!(options) - - # Pass through args exactly as we received them. - args << options - @reflection.klass.find(*args) end end -- cgit v1.2.3 From 08312e9958b84de9aef07ba157eed04750567da4 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 27 Dec 2009 23:29:38 +0530 Subject: Return a new relation when blank is supplied to relation.joins --- activerecord/lib/active_record/relation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 4aa460a601..d1f4dc2101 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -69,7 +69,7 @@ module ActiveRecord end def joins(join, join_type = nil) - return self if join.blank? + return create_new_relation if join.blank? join_relation = case join when String -- cgit v1.2.3 From a8b10a2a8d6f8d861453803264b9ef1b0cadbc6b Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 28 Dec 2009 01:08:34 +0530 Subject: Add relation#merge to merge two relations --- activerecord/lib/active_record/relation.rb | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index d1f4dc2101..967e429fc3 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -12,6 +12,18 @@ module ActiveRecord @loaded = false end + def merge(r) + joins(r.relation.joins(r.relation)). + group(r.send(:group_clauses).join(', ')). + order(r.send(:order_clauses).join(', ')). + where(r.send(:where_clause)). + limit(r.taken). + offset(r.skipped). + select(r.send(:select_clauses).join(', ')) + end + + alias :& :merge + def preload(*associations) create_new_relation(@relation, @readonly, @associations_to_preload + Array.wrap(associations)) end @@ -25,7 +37,7 @@ module ActiveRecord end def select(selects) - create_new_relation(@relation.project(selects)) + selects.present? ? create_new_relation(@relation.project(selects)) : create_new_relation end # TODO : This is temporary. We need .from in Arel. @@ -37,11 +49,11 @@ module ActiveRecord end def group(groups) - create_new_relation(@relation.group(groups)) + groups.present? ? create_new_relation(@relation.group(groups)) : create_new_relation end def order(orders) - create_new_relation(@relation.order(orders)) + orders.present? ? create_new_relation(@relation.order(orders)) : create_new_relation end def reverse_order @@ -57,11 +69,11 @@ module ActiveRecord end def limit(limits) - create_new_relation(@relation.take(limits)) + limits.present? ? create_new_relation(@relation.take(limits)) : create_new_relation end def offset(offsets) - create_new_relation(@relation.skip(offsets)) + offsets.present? ? create_new_relation(@relation.skip(offsets)) : create_new_relation end def on(join) -- cgit v1.2.3 From 51a1d5a6708726ae03deb5869f36e7d5cca22a6e Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 28 Dec 2009 01:33:20 +0530 Subject: Handle preloads and eager loads when merging relations --- activerecord/lib/active_record/relation.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 967e429fc3..d0f6c5a63f 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -2,7 +2,7 @@ module ActiveRecord class Relation delegate :to_sql, :to => :relation delegate :length, :collect, :find, :map, :each, :to => :to_a - attr_reader :relation, :klass + attr_reader :relation, :klass, :associations_to_preload, :eager_load_associations def initialize(klass, relation, readonly = false, preload = [], eager_load = []) @klass, @relation = klass, relation @@ -19,7 +19,9 @@ module ActiveRecord where(r.send(:where_clause)). limit(r.taken). offset(r.skipped). - select(r.send(:select_clauses).join(', ')) + select(r.send(:select_clauses).join(', ')). + eager_load(r.eager_load_associations). + preload(r.associations_to_preload) end alias :& :merge -- cgit v1.2.3 From e9e20ab6013424c098c88d357d210068a32a1b39 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 28 Dec 2009 03:19:31 +0530 Subject: Use arel#from instead of the current hack --- activerecord/lib/active_record/relation.rb | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index d0f6c5a63f..a789337027 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -21,7 +21,8 @@ module ActiveRecord offset(r.skipped). select(r.send(:select_clauses).join(', ')). eager_load(r.eager_load_associations). - preload(r.associations_to_preload) + preload(r.associations_to_preload). + from(r.send(:sources).any? ? r.send(:from_clauses) : nil) end alias :& :merge @@ -42,12 +43,8 @@ module ActiveRecord selects.present? ? create_new_relation(@relation.project(selects)) : create_new_relation end - # TODO : This is temporary. We need .from in Arel. - attr_writer :from def from(from) - relation = create_new_relation - relation.from = from - relation + from.present? ? create_new_relation(@relation.from(from)) : create_new_relation end def group(groups) @@ -130,7 +127,7 @@ module ActiveRecord :conditions => where_clause, :limit => @relation.taken, :offset => @relation.skipped, - :from => @from + :from => (@relation.send(:from_clauses) if @relation.send(:sources).any?) }, ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations, nil)) end @@ -303,9 +300,7 @@ module ActiveRecord end def create_new_relation(relation = @relation, readonly = @readonly, preload = @associations_to_preload, eager_load = @eager_load_associations) - r = self.class.new(@klass, relation, readonly, preload, eager_load) - r.from = @from - r + self.class.new(@klass, relation, readonly, preload, eager_load) end def where_clause(join_string = "\n\tAND ") -- cgit v1.2.3 From a0ff5f82c37f312a808dbbdb6191fbf20a750775 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 28 Dec 2009 03:22:33 +0530 Subject: Dont delegate relation#find to to_a --- activerecord/lib/active_record/relation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index a789337027..530402bf5d 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -1,7 +1,7 @@ module ActiveRecord class Relation delegate :to_sql, :to => :relation - delegate :length, :collect, :find, :map, :each, :to => :to_a + delegate :length, :collect, :map, :each, :to => :to_a attr_reader :relation, :klass, :associations_to_preload, :eager_load_associations def initialize(klass, relation, readonly = false, preload = [], eager_load = []) -- cgit v1.2.3 From 6a776dcc9df437c0bcc3c1ff1d2a79966264bac9 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 28 Dec 2009 03:49:35 +0530 Subject: Use relation.from when constructing a relation --- activerecord/lib/active_record/associations.rb | 5 +++-- activerecord/lib/active_record/base.rb | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 2735bc5141..c23c9f63f1 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1707,7 +1707,7 @@ module ActiveRecord def construct_finder_arel_with_included_associations(options, join_dependency) scope = scope(:find) - relation = arel_table((scope && scope[:from]) || options[:from]) + relation = arel_table for association in join_dependency.join_associations relation = association.join_relation(relation) @@ -1717,7 +1717,8 @@ module ActiveRecord select(column_aliases(join_dependency)). group(construct_group(options[:group], options[:having], scope)). order(construct_order(options[:order], scope)). - where(construct_conditions(options[:conditions], scope)) + where(construct_conditions(options[:conditions], scope)). + from((scope && scope[:from]) || options[:from]) relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency)) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit]) relation = relation.limit(construct_limit(options[:limit], scope)) if using_limitable_reflections?(join_dependency.reflections) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index cad75f80e5..bbc5bd77c5 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1563,7 +1563,7 @@ module ActiveRecord #:nodoc: # TODO add lock to Arel validate_find_options(options) - relation = arel_table(options[:from]). + relation = arel_table. joins(construct_join(options[:joins], scope)). where(construct_conditions(options[:conditions], scope)). select(options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))). -- cgit v1.2.3 From 1c26ba486c23f229a12fea6ccad33e6cb3122b91 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 28 Dec 2009 03:56:20 +0530 Subject: Add Model.from and association_collection#from finder methods --- activerecord/lib/active_record/associations/association_collection.rb | 2 +- activerecord/lib/active_record/base.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index ac970f02b1..d2c61cdc78 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -21,7 +21,7 @@ module ActiveRecord construct_sql end - delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :to => :scoped + delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :to => :scoped def select(select = nil, &block) if block_given? diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index bbc5bd77c5..3b880ce17f 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -652,7 +652,7 @@ module ActiveRecord #:nodoc: end end - delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :to => :scoped + delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :to => :scoped # A convenience wrapper for find(:first, *args). You can pass in all the # same arguments to this method as you can to find(:first). -- cgit v1.2.3 From a3c1db4e444baa8ae4f8d968fab786e03c93f413 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 28 Dec 2009 12:33:35 +0530 Subject: Add Model.lock and relation#lock now that arel has locking --- .../lib/active_record/associations/association_collection.rb | 2 +- activerecord/lib/active_record/base.rb | 5 ++++- activerecord/lib/active_record/relation.rb | 11 +++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index d2c61cdc78..b3a69ab0c0 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -21,7 +21,7 @@ module ActiveRecord construct_sql end - delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :to => :scoped + delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :lock, :to => :scoped def select(select = nil, &block) if block_given? diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 3b880ce17f..a887b0c571 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -652,7 +652,7 @@ module ActiveRecord #:nodoc: end end - delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :to => :scoped + delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :lock, :to => :scoped # A convenience wrapper for find(:first, *args). You can pass in all the # same arguments to this method as you can to find(:first). @@ -1573,6 +1573,9 @@ module ActiveRecord #:nodoc: offset(construct_offset(options[:offset], scope)). from(options[:from]) + lock = (scope && scope[:lock]) || options[:lock] + relation = relation.lock if lock.present? + relation = relation.readonly if options[:readonly] relation diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 530402bf5d..a7f62abe1d 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -55,6 +55,17 @@ module ActiveRecord orders.present? ? create_new_relation(@relation.order(orders)) : create_new_relation end + def lock(locks = true) + case locks + when String + create_new_relation(@relation.lock(locks)) + when TrueClass, NilClass + create_new_relation(@relation.lock) + else + create_new_relation + end + end + def reverse_order relation = create_new_relation relation.instance_variable_set(:@orders, nil) -- cgit v1.2.3 From 9f4e98330b3a7f85a4bca91fca062106ffbee2bf Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 28 Dec 2009 12:57:35 +0530 Subject: Remove unused construct_finder_sql --- activerecord/lib/active_record/base.rb | 4 ---- 1 file changed, 4 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index a887b0c571..da1341a846 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1596,10 +1596,6 @@ module ActiveRecord #:nodoc: relation end - def construct_finder_sql(options, scope = scope(:find)) - construct_finder_arel(options, scope).to_sql - end - def construct_join(joins, scope) merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins]) case merged_joins -- cgit v1.2.3 From 92c982d973c3e3125982309ff8bb6c22608696c5 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 28 Dec 2009 14:20:54 +0530 Subject: Relation#readonly(false) should toggle the readonly flag --- activerecord/lib/active_record/relation.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index a7f62abe1d..ff10df96f2 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -35,12 +35,17 @@ module ActiveRecord create_new_relation(@relation, @readonly, @associations_to_preload, @eager_load_associations + Array.wrap(associations)) end - def readonly - create_new_relation(@relation, true) + def readonly(status = true) + status.nil? ? create_new_relation : create_new_relation(@relation, status) end def select(selects) - selects.present? ? create_new_relation(@relation.project(selects)) : create_new_relation + if selects.present? + frozen = @relation.joins(relation).present? ? false : @readonly + create_new_relation(@relation.project(selects), frozen) + else + create_new_relation + end end def from(from) @@ -106,7 +111,7 @@ module ActiveRecord @relation.join(join, join_type) end - create_new_relation(join_relation) + create_new_relation(join_relation, true) end def where(*args) -- cgit v1.2.3 From b95cc72429f83304b8e882c3637dfb3135a571ed Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 28 Dec 2009 14:24:52 +0530 Subject: Raise ArgumentError when trying to merge relations of different classes --- activerecord/lib/active_record/relation.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index ff10df96f2..ef480cfb29 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -13,6 +13,8 @@ module ActiveRecord end def merge(r) + raise ArgumentError, "Cannot merge a #{r.klass.name} relation with #{@klass.name} relation" if r.klass != @klass + joins(r.relation.joins(r.relation)). group(r.send(:group_clauses).join(', ')). order(r.send(:order_clauses).join(', ')). -- cgit v1.2.3 From 5156507e13bb17f1852576acfd921a39e06de175 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 28 Dec 2009 14:33:56 +0530 Subject: Remove locking related unused code --- activerecord/lib/active_record/base.rb | 8 -------- .../connection_adapters/abstract/database_statements.rb | 12 ------------ .../lib/active_record/connection_adapters/sqlite_adapter.rb | 6 ------ 3 files changed, 26 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index da1341a846..30f0207aab 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1702,14 +1702,6 @@ module ActiveRecord #:nodoc: o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)} end - # The optional scope argument is for the current :find scope. - # The :lock option has precedence over a scoped :lock. - def add_lock!(sql, options, scope = :auto) - scope = scope(:find) if :auto == scope - options = options.reverse_merge(:lock => scope[:lock]) if scope - connection.add_lock!(sql, options) - end - def type_condition(table_alias=nil) quoted_table_alias = self.connection.quote_table_name(table_alias || table_name) quoted_inheritance_column = connection.quote_column_name(inheritance_column) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index be89873632..027d736484 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -181,18 +181,6 @@ module ActiveRecord # done if the transaction block raises an exception or returns false. def rollback_db_transaction() end - # Appends a locking clause to an SQL statement. - # This method *modifies* the +sql+ parameter. - # # SELECT * FROM suppliers FOR UPDATE - # add_lock! 'SELECT * FROM suppliers', :lock => true - # add_lock! 'SELECT * FROM suppliers', :lock => ' FOR UPDATE' - def add_lock!(sql, options) - case lock = options[:lock] - when true; sql << ' FOR UPDATE' - when String; sql << " #{lock}" - end - end - def default_sequence_name(table, column) nil end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index c9c2892ba4..78b897add6 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -183,12 +183,6 @@ module ActiveRecord catch_schema_changes { @connection.rollback } end - # SELECT ... FOR UPDATE is redundant since the table is locked. - def add_lock!(sql, options) #:nodoc: - sql - end - - # SCHEMA STATEMENTS ======================================== def tables(name = nil) #:nodoc: -- cgit v1.2.3 From 02207dc02c10f2b00a84594962d7bf6ffcf539a9 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 28 Dec 2009 16:16:21 +0530 Subject: Add Model.readonly and association_collection#readonly finder method --- activerecord/lib/active_record/associations/association_collection.rb | 2 +- activerecord/lib/active_record/base.rb | 2 +- activerecord/lib/active_record/relation.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index b3a69ab0c0..8bc64a3a93 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -21,7 +21,7 @@ module ActiveRecord construct_sql end - delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :lock, :to => :scoped + delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :lock, :readonly, :to => :scoped def select(select = nil, &block) if block_given? diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 30f0207aab..a9b011dd76 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -652,7 +652,7 @@ module ActiveRecord #:nodoc: end end - delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :lock, :to => :scoped + delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :lock, :readonly, :to => :scoped # A convenience wrapper for find(:first, *args). You can pass in all the # same arguments to this method as you can to find(:first). diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index ef480cfb29..93f7b74c68 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -1,7 +1,7 @@ module ActiveRecord class Relation delegate :to_sql, :to => :relation - delegate :length, :collect, :map, :each, :to => :to_a + delegate :length, :collect, :map, :each, :all?, :to => :to_a attr_reader :relation, :klass, :associations_to_preload, :eager_load_associations def initialize(klass, relation, readonly = false, preload = [], eager_load = []) -- cgit v1.2.3 From aefa975fdde01b1beaacbe065fe4b2bad69295d3 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 28 Dec 2009 16:20:40 +0530 Subject: Remove the todo note for arel#lock --- activerecord/lib/active_record/base.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index a9b011dd76..3135234706 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1560,7 +1560,6 @@ module ActiveRecord #:nodoc: end def construct_finder_arel(options = {}, scope = scope(:find)) - # TODO add lock to Arel validate_find_options(options) relation = arel_table. -- cgit v1.2.3 From 8f5d9eb0e2d05c5ca10c313bb47dbcab335c6fa7 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 28 Dec 2009 18:38:28 +0530 Subject: Add Relation#count --- activerecord/lib/active_record/relation.rb | 45 ++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 93f7b74c68..cb252eea70 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -204,6 +204,20 @@ module ActiveRecord end end + def count(*args) + column_name, options = construct_count_options_from_args(*args) + distinct = options[:distinct] ? true : false + + column = if @klass.column_names.include?(column_name.to_s) + Arel::Attribute.new(@relation.table, column_name) + else + Arel::SqlLiteral.new(column_name == :all ? "*" : column_name.to_s) + end + + relation = select(column.count(distinct)) + @klass.connection.select_value(relation.to_sql).to_i + end + def destroy_all to_a.each {|object| object.destroy} reset @@ -337,5 +351,36 @@ module ActiveRecord }.join(',') end + def construct_count_options_from_args(*args) + options = {} + column_name = :all + + # We need to handle + # count() + # count(:column_name=:all) + # count(options={}) + # count(column_name=:all, options={}) + # selects specified by scopes + + # TODO : relation.projections only works when .select() was last in the chain. Fix it! + case args.size + when 0 + column_name = @relation.send(:select_clauses).join(', ') if @relation.respond_to?(:projections) && @relation.projections.present? + when 1 + if args[0].is_a?(Hash) + column_name = @relation.send(:select_clauses).join(', ') if @relation.respond_to?(:projections) && @relation.projections.present? + options = args[0] + else + column_name = args[0] + end + when 2 + column_name, options = args + else + raise ArgumentError, "Unexpected parameters passed to count(): #{args.inspect}" + end + + [column_name || :all, options] + end + end end -- cgit v1.2.3 From e8ca22d129c1e93574e770dd69dc964be6686469 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 28 Dec 2009 19:12:15 +0530 Subject: Move Relation calculation methods to a separate module --- activerecord/lib/active_record.rb | 1 + activerecord/lib/active_record/relation.rb | 46 +------------------ .../lib/active_record/relational_calculations.rb | 52 ++++++++++++++++++++++ 3 files changed, 54 insertions(+), 45 deletions(-) create mode 100644 activerecord/lib/active_record/relational_calculations.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 2cfd528f2c..7031c67539 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -48,6 +48,7 @@ module ActiveRecord autoload :Attributes autoload :AutosaveAssociation autoload :Relation + autoload :RelationalCalculations autoload :Base autoload :Batches autoload :Calculations diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index cb252eea70..291e97ff83 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -4,6 +4,7 @@ module ActiveRecord delegate :length, :collect, :map, :each, :all?, :to => :to_a attr_reader :relation, :klass, :associations_to_preload, :eager_load_associations + include RelationalCalculations def initialize(klass, relation, readonly = false, preload = [], eager_load = []) @klass, @relation = klass, relation @readonly = readonly @@ -204,20 +205,6 @@ module ActiveRecord end end - def count(*args) - column_name, options = construct_count_options_from_args(*args) - distinct = options[:distinct] ? true : false - - column = if @klass.column_names.include?(column_name.to_s) - Arel::Attribute.new(@relation.table, column_name) - else - Arel::SqlLiteral.new(column_name == :all ? "*" : column_name.to_s) - end - - relation = select(column.count(distinct)) - @klass.connection.select_value(relation.to_sql).to_i - end - def destroy_all to_a.each {|object| object.destroy} reset @@ -351,36 +338,5 @@ module ActiveRecord }.join(',') end - def construct_count_options_from_args(*args) - options = {} - column_name = :all - - # We need to handle - # count() - # count(:column_name=:all) - # count(options={}) - # count(column_name=:all, options={}) - # selects specified by scopes - - # TODO : relation.projections only works when .select() was last in the chain. Fix it! - case args.size - when 0 - column_name = @relation.send(:select_clauses).join(', ') if @relation.respond_to?(:projections) && @relation.projections.present? - when 1 - if args[0].is_a?(Hash) - column_name = @relation.send(:select_clauses).join(', ') if @relation.respond_to?(:projections) && @relation.projections.present? - options = args[0] - else - column_name = args[0] - end - when 2 - column_name, options = args - else - raise ArgumentError, "Unexpected parameters passed to count(): #{args.inspect}" - end - - [column_name || :all, options] - end - end end diff --git a/activerecord/lib/active_record/relational_calculations.rb b/activerecord/lib/active_record/relational_calculations.rb new file mode 100644 index 0000000000..10eb992167 --- /dev/null +++ b/activerecord/lib/active_record/relational_calculations.rb @@ -0,0 +1,52 @@ +module ActiveRecord + module RelationalCalculations + + def count(*args) + column_name, options = construct_count_options_from_args(*args) + distinct = options[:distinct] ? true : false + + column = if @klass.column_names.include?(column_name.to_s) + Arel::Attribute.new(@relation.table, column_name) + else + Arel::SqlLiteral.new(column_name == :all ? "*" : column_name.to_s) + end + + relation = select(column.count(distinct)) + @klass.connection.select_value(relation.to_sql).to_i + end + + private + + def construct_count_options_from_args(*args) + options = {} + column_name = :all + + # We need to handle + # count() + # count(:column_name=:all) + # count(options={}) + # count(column_name=:all, options={}) + # selects specified by scopes + + # TODO : relation.projections only works when .select() was last in the chain. Fix it! + case args.size + when 0 + column_name = @relation.send(:select_clauses).join(', ') if @relation.respond_to?(:projections) && @relation.projections.present? + when 1 + if args[0].is_a?(Hash) + column_name = @relation.send(:select_clauses).join(', ') if @relation.respond_to?(:projections) && @relation.projections.present? + options = args[0] + else + column_name = args[0] + end + when 2 + column_name, options = args + else + raise ArgumentError, "Unexpected parameters passed to count(): #{args.inspect}" + end + + [column_name || :all, options] + end + + end +end -- cgit v1.2.3 From fc85c665271578e55e7fe90a721ca1533289d923 Mon Sep 17 00:00:00 2001 From: George Ogata Date: Thu, 26 Nov 2009 00:05:57 -0500 Subject: Set inverse for #replace on a has_one association. [#3513 state:resolved] Signed-off-by: Eloy Duran --- activerecord/lib/active_record/associations/has_one_association.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index b85a40b2e5..081d6233c4 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -57,6 +57,7 @@ module ActiveRecord @target = (AssociationProxy === obj ? obj.target : obj) end + set_inverse_instance(obj, @owner) @loaded = true unless @owner.new_record? or obj.nil? or dont_save -- cgit v1.2.3 From 6c8c85bc1eaf1639ea0df5f356e7105c74d128b2 Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Thu, 17 Dec 2009 11:38:44 +0000 Subject: Add more tests for the various ways we can assign objects to associations. [#3513 state:resolved] Get rid of a duplicate set_inverse_instance call if you use new_record(true) (e.g. you want to replace the existing instance). Signed-off-by: Eloy Duran --- activerecord/lib/active_record/associations/has_one_association.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 081d6233c4..ea769fd48b 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -121,10 +121,9 @@ module ActiveRecord else record[@reflection.primary_key_name] = @owner.id unless @owner.new_record? self.target = record + set_inverse_instance(record, @owner) end - set_inverse_instance(record, @owner) - record end -- cgit v1.2.3 From 81ca0cf2b074f4b868a84c427ef155607a956119 Mon Sep 17 00:00:00 2001 From: George Ogata Date: Sun, 29 Nov 2009 00:46:09 -0500 Subject: Add inverse polymorphic association support. [#3520 state:resolved] Signed-off-by: Eloy Duran --- .../belongs_to_polymorphic_association.rb | 39 ++++++++++++++++------ activerecord/lib/active_record/reflection.rb | 14 ++++++-- 2 files changed, 41 insertions(+), 12 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb index 67e18d692d..9678300003 100644 --- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb @@ -13,6 +13,7 @@ module ActiveRecord @updated = true end + set_inverse_instance(record, @owner) loaded record end @@ -22,19 +23,37 @@ module ActiveRecord end private + + # NOTE - for now, we're only supporting inverse setting from belongs_to back onto + # has_one associations. + def we_can_set_the_inverse_on_this?(record) + @reflection.has_inverse? && @reflection.polymorphic_inverse_of(record.class).macro == :has_one + end + + def set_inverse_instance(record, instance) + return if record.nil? || !we_can_set_the_inverse_on_this?(record) + inverse_relationship = @reflection.polymorphic_inverse_of(record.class) + unless inverse_relationship.nil? + record.send(:"set_#{inverse_relationship.name}_target", instance) + end + end + def find_target return nil if association_class.nil? - if @reflection.options[:conditions] - association_class.find( - @owner[@reflection.primary_key_name], - :select => @reflection.options[:select], - :conditions => conditions, - :include => @reflection.options[:include] - ) - else - association_class.find(@owner[@reflection.primary_key_name], :select => @reflection.options[:select], :include => @reflection.options[:include]) - end + target = + if @reflection.options[:conditions] + association_class.find( + @owner[@reflection.primary_key_name], + :select => @reflection.options[:select], + :conditions => conditions, + :include => @reflection.options[:include] + ) + else + association_class.find(@owner[@reflection.primary_key_name], :select => @reflection.options[:select], :include => @reflection.options[:include]) + end + set_inverse_instance(target, @owner) if target + target end def foreign_key_present diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index db5d2b25ed..72f7df32c7 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -214,8 +214,10 @@ module ActiveRecord end def check_validity_of_inverse! - if has_inverse? && inverse_of.nil? - raise InverseOfAssociationNotFoundError.new(self) + unless options[:polymorphic] + if has_inverse? && inverse_of.nil? + raise InverseOfAssociationNotFoundError.new(self) + end end end @@ -242,6 +244,14 @@ module ActiveRecord end end + def polymorphic_inverse_of(associated_class) + if has_inverse? + associated_class.reflect_on_association(options[:inverse_of]) + else + nil + end + end + private def derive_class_name class_name = name.to_s.camelize -- cgit v1.2.3 From 6a74ee7f4deea4a44520d3fcc9120e0bb848823f Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Thu, 17 Dec 2009 12:19:10 +0000 Subject: Provide a slightly more robust we_can_set_the_inverse_on_this? method for polymorphic belongs_to associations. [#3520 state:resolved] Also add a new test for polymorphic belongs_to that test direct accessor assignment, not just .replace assignment. Signed-off-by: Eloy Duran --- .../associations/belongs_to_polymorphic_association.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb index 9678300003..f6edd6383c 100644 --- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb @@ -27,7 +27,12 @@ module ActiveRecord # NOTE - for now, we're only supporting inverse setting from belongs_to back onto # has_one associations. def we_can_set_the_inverse_on_this?(record) - @reflection.has_inverse? && @reflection.polymorphic_inverse_of(record.class).macro == :has_one + if @reflection.has_inverse? + inverse_association = @reflection.polymorphic_inverse_of(record.class) + inverse_association && inverse_association.macro == :has_one + else + false + end end def set_inverse_instance(record, instance) @@ -52,7 +57,7 @@ module ActiveRecord else association_class.find(@owner[@reflection.primary_key_name], :select => @reflection.options[:select], :include => @reflection.options[:include]) end - set_inverse_instance(target, @owner) if target + set_inverse_instance(target, @owner) target end -- cgit v1.2.3 From ff508640e28914da2b546f6a8c9f215bab201b61 Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Mon, 28 Dec 2009 14:13:33 +0100 Subject: Make polymorphic_inverse_of in Reflection throw an InverseOfAssociationNotFoundError if the supplied class doesn't have the appropriate association. [#3520 state:resolved] Signed-off-by: Eloy Duran --- activerecord/lib/active_record/associations.rb | 4 ++-- activerecord/lib/active_record/reflection.rb | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index c23c9f63f1..ff8c63bc91 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -3,8 +3,8 @@ require 'active_support/core_ext/enumerable' module ActiveRecord class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc: - def initialize(reflection) - super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{reflection.class_name})") + def initialize(reflection, associated_class = nil) + super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})") end end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 72f7df32c7..b751c9ad68 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -239,16 +239,16 @@ module ActiveRecord def inverse_of if has_inverse? @inverse_of ||= klass.reflect_on_association(options[:inverse_of]) - else - nil end end def polymorphic_inverse_of(associated_class) if has_inverse? - associated_class.reflect_on_association(options[:inverse_of]) - else - nil + if inverse_relationship = associated_class.reflect_on_association(options[:inverse_of]) + inverse_relationship + else + raise InverseOfAssociationNotFoundError.new(self, associated_class) + end end end -- cgit v1.2.3 From 9c771a9608f54ebdfcb6fca819c83038489ce50d Mon Sep 17 00:00:00 2001 From: Eloy Duran Date: Tue, 15 Dec 2009 16:40:02 +0100 Subject: Make sure to not add autosave callbacks multiple times. [#3575 state:resolved] This makes sure that, in a HABTM association, only one join record is craeted. --- .../lib/active_record/autosave_association.rb | 40 ++++++++++++++-------- .../lib/active_record/nested_attributes.rb | 4 +-- 2 files changed, 27 insertions(+), 17 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index c0d8904bc8..44c668b619 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -155,6 +155,13 @@ module ActiveRecord # Adds a validate and save callback for the association as specified by # the +reflection+. + # + # For performance reasons, we don't check whether to validate at runtime, + # but instead only define the method and callback when needed. However, + # this can change, for instance, when using nested attributes. Since we + # don't want the callbacks to get defined multiple times, there are + # guards that check if the save or validation methods have already been + # defined before actually defining them. def add_autosave_association_callbacks(reflection) save_method = "autosave_associated_records_for_#{reflection.name}" validation_method = "validate_associated_records_for_#{reflection.name}" @@ -162,28 +169,33 @@ module ActiveRecord case reflection.macro when :has_many, :has_and_belongs_to_many - before_save :before_save_collection_association + unless method_defined?(save_method) + before_save :before_save_collection_association - define_method(save_method) { save_collection_association(reflection) } - # Doesn't use after_save as that would save associations added in after_create/after_update twice - after_create save_method - after_update save_method + define_method(save_method) { save_collection_association(reflection) } + # Doesn't use after_save as that would save associations added in after_create/after_update twice + after_create save_method + after_update save_method + end - if force_validation || (reflection.macro == :has_many && reflection.options[:validate] != false) + if !method_defined?(validation_method) && + (force_validation || (reflection.macro == :has_many && reflection.options[:validate] != false)) define_method(validation_method) { validate_collection_association(reflection) } validate validation_method end else - case reflection.macro - when :has_one - define_method(save_method) { save_has_one_association(reflection) } - after_save save_method - when :belongs_to - define_method(save_method) { save_belongs_to_association(reflection) } - before_save save_method + unless method_defined?(save_method) + case reflection.macro + when :has_one + define_method(save_method) { save_has_one_association(reflection) } + after_save save_method + when :belongs_to + define_method(save_method) { save_belongs_to_association(reflection) } + before_save save_method + end end - if force_validation + if !method_defined?(validation_method) && force_validation define_method(validation_method) { validate_single_association(reflection) } validate validation_method end diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index ca3110a374..386f0e7ca7 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -235,7 +235,7 @@ module ActiveRecord end reflection.options[:autosave] = true - + add_autosave_association_callbacks(reflection) self.nested_attributes_options[association_name.to_sym] = options if options[:reject_if] == :all_blank @@ -250,8 +250,6 @@ module ActiveRecord assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes) end }, __FILE__, __LINE__ - - add_autosave_association_callbacks(reflection) else raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?" end -- cgit v1.2.3 From 91e28aae8649c503e81d66ad6829403ccc2c6571 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 29 Dec 2009 00:07:46 +0530 Subject: Add Model.having and Relation#having --- activerecord/lib/active_record/associations.rb | 6 ++++-- .../associations/association_collection.rb | 2 +- activerecord/lib/active_record/base.rb | 17 +++-------------- activerecord/lib/active_record/calculations.rb | 2 +- activerecord/lib/active_record/relation.rb | 12 ++++++++++++ 5 files changed, 21 insertions(+), 18 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index c23c9f63f1..7242ebf387 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1715,7 +1715,8 @@ module ActiveRecord relation = relation.joins(construct_join(options[:joins], scope)). select(column_aliases(join_dependency)). - group(construct_group(options[:group], options[:having], scope)). + group(options[:group] || (scope && scope[:group])). + having(options[:having] || (scope && scope[:having])). order(construct_order(options[:order], scope)). where(construct_conditions(options[:conditions], scope)). from((scope && scope[:from]) || options[:from]) @@ -1759,7 +1760,8 @@ module ActiveRecord relation = relation.joins(construct_join(options[:joins], scope)). where(construct_conditions(options[:conditions], scope)). - group(construct_group(options[:group], options[:having], scope)). + group(options[:group] || (scope && scope[:group])). + having(options[:having] || (scope && scope[:having])). order(construct_order(options[:order], scope)). limit(construct_limit(options[:limit], scope)). offset(construct_limit(options[:offset], scope)). diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 8bc64a3a93..56b2a90138 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -21,7 +21,7 @@ module ActiveRecord construct_sql end - delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :lock, :readonly, :to => :scoped + delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :lock, :readonly, :having, :to => :scoped def select(select = nil, &block) if block_given? diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 3135234706..767109474d 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -652,7 +652,7 @@ module ActiveRecord #:nodoc: end end - delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :lock, :readonly, :to => :scoped + delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :lock, :readonly, :having, :to => :scoped # A convenience wrapper for find(:first, *args). You can pass in all the # same arguments to this method as you can to find(:first). @@ -1566,7 +1566,8 @@ module ActiveRecord #:nodoc: joins(construct_join(options[:joins], scope)). where(construct_conditions(options[:conditions], scope)). select(options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))). - group(construct_group(options[:group], options[:having], scope)). + group(options[:group] || (scope && scope[:group])). + having(options[:having] || (scope && scope[:having])). order(construct_order(options[:order], scope)). limit(construct_limit(options[:limit], scope)). offset(construct_offset(options[:offset], scope)). @@ -1611,18 +1612,6 @@ module ActiveRecord #:nodoc: end end - def construct_group(group, having, scope) - sql = '' - if group - sql << group.to_s - sql << " HAVING #{sanitize_sql_for_conditions(having)}" if having - elsif scope && (scoped_group = scope[:group]) - sql << scoped_group.to_s - sql << " HAVING #{sanitize_sql_for_conditions(scope[:having])}" if scope[:having] - end - sql - end - def construct_order(order, scope) orders = [] diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index fcba23dc0d..59811c40a8 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -194,7 +194,7 @@ module ActiveRecord options[:select] << ", #{group_field} AS #{group_alias}" - relation = relation.select(options[:select]).group(construct_group(options[:group], options[:having], nil)) + relation = relation.select(options[:select]).group(options[:group]).having(options[:having]) calculated_data = connection.select_all(relation.to_sql) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 291e97ff83..c7a74b7763 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -55,6 +55,18 @@ module ActiveRecord from.present? ? create_new_relation(@relation.from(from)) : create_new_relation end + def having(*args) + return create_new_relation if args.blank? + + if [String, Hash, Array].include?(args.first.class) + havings = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first) + else + havings = args.first + end + + create_new_relation(@relation.having(havings)) + end + def group(groups) groups.present? ? create_new_relation(@relation.group(groups)) : create_new_relation end -- cgit v1.2.3 From 07b615fb897017d7acfaafa88606bc88be30f6e4 Mon Sep 17 00:00:00 2001 From: Michael Siebert Date: Mon, 28 Dec 2009 19:02:01 +0100 Subject: Add an :update_only option to accepts_nested_attributes_for for to-one associations. [#2563 state:resolved] Signed-off-by: Eloy Duran --- .../lib/active_record/nested_attributes.rb | 26 +++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 386f0e7ca7..5143e804d1 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -212,6 +212,11 @@ module ActiveRecord # nested attributes array exceeds the specified limit, NestedAttributes::TooManyRecords # exception is raised. If omitted, any number associations can be processed. # Note that the :limit option is only applicable to one-to-many associations. + # [:update_only] + # Allows to specify that the an existing record can only be updated. + # A new record in only created when there is no existing record. This + # option only works for on-to-one associations and is ignored for + # collection associations. This option is off by default. # # Examples: # # creates avatar_attributes= @@ -221,9 +226,9 @@ module ActiveRecord # # creates avatar_attributes= and posts_attributes= # accepts_nested_attributes_for :avatar, :posts, :allow_destroy => true def accepts_nested_attributes_for(*attr_names) - options = { :allow_destroy => false } + options = { :allow_destroy => false, :update_only => false } options.update(attr_names.extract_options!) - options.assert_valid_keys(:allow_destroy, :reject_if, :limit) + options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only) attr_names.each do |association_name| if reflection = reflect_on_association(association_name) @@ -288,6 +293,13 @@ module ActiveRecord # record’s id, then the existing record will be modified. Otherwise a new # record will be built. # + # If update_only is true, a new record is only created when no object exists, + # otherwise it will be updated + # + # If update_only is false and the given attributes include an :id + # that matches the existing record’s id, then the existing record will be + # modified. Otherwise a new record will be built. + # # If the given attributes include a matching :id attribute _and_ a # :_destroy key set to a truthy value, then the existing record # will be marked for destruction. @@ -295,7 +307,15 @@ module ActiveRecord options = self.nested_attributes_options[association_name] attributes = attributes.with_indifferent_access - if attributes['id'].blank? + if options[:update_only] + if existing_record = send(association_name) + assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) + else + unless reject_new_record?(association_name, attributes) + send("build_#{association_name}", attributes.except(*UNASSIGNABLE_KEYS)) + end + end + elsif attributes['id'].blank? unless reject_new_record?(association_name, attributes) method = "build_#{association_name}" if respond_to?(method) -- cgit v1.2.3 From c23fbd0d475612fe9cd493bd08c8da2f8d7e6f03 Mon Sep 17 00:00:00 2001 From: Eloy Duran Date: Mon, 28 Dec 2009 21:08:20 +0100 Subject: Refactored previous changes to nested attributes. --- .../lib/active_record/nested_attributes.rb | 50 ++++++++-------------- 1 file changed, 18 insertions(+), 32 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 5143e804d1..ff3a51d5c0 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -213,9 +213,9 @@ module ActiveRecord # exception is raised. If omitted, any number associations can be processed. # Note that the :limit option is only applicable to one-to-many associations. # [:update_only] - # Allows to specify that the an existing record can only be updated. - # A new record in only created when there is no existing record. This - # option only works for on-to-one associations and is ignored for + # Allows you to specify that an existing record may only be updated. + # A new record may only be created when there is no existing record. + # This option only works for one-to-one associations and is ignored for # collection associations. This option is off by default. # # Examples: @@ -248,7 +248,7 @@ module ActiveRecord end # def pirate_attributes=(attributes) - # assign_nested_attributes_for_one_to_one_association(:pirate, attributes, false) + # assign_nested_attributes_for_one_to_one_association(:pirate, attributes) # end class_eval %{ def #{association_name}_attributes=(attributes) @@ -289,43 +289,29 @@ module ActiveRecord # Assigns the given attributes to the association. # - # If the given attributes include an :id that matches the existing - # record’s id, then the existing record will be modified. Otherwise a new - # record will be built. - # - # If update_only is true, a new record is only created when no object exists, - # otherwise it will be updated - # # If update_only is false and the given attributes include an :id # that matches the existing record’s id, then the existing record will be - # modified. Otherwise a new record will be built. + # modified. If update_only is true, a new record is only created when no + # object exists. Otherwise a new record will be built. # - # If the given attributes include a matching :id attribute _and_ a - # :_destroy key set to a truthy value, then the existing record - # will be marked for destruction. + # If the given attributes include a matching :id attribute, or + # update_only is true, and a :_destroy key set to a truthy value, + # then the existing record will be marked for destruction. def assign_nested_attributes_for_one_to_one_association(association_name, attributes) options = self.nested_attributes_options[association_name] attributes = attributes.with_indifferent_access + check_existing_record = (options[:update_only] || !attributes['id'].blank?) - if options[:update_only] - if existing_record = send(association_name) - assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) + if check_existing_record && (record = send(association_name)) && + (options[:update_only] || record.id.to_s == attributes['id'].to_s) + assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy]) + elsif !reject_new_record?(association_name, attributes) + method = "build_#{association_name}" + if respond_to?(method) + send(method, attributes.except(*UNASSIGNABLE_KEYS)) else - unless reject_new_record?(association_name, attributes) - send("build_#{association_name}", attributes.except(*UNASSIGNABLE_KEYS)) - end - end - elsif attributes['id'].blank? - unless reject_new_record?(association_name, attributes) - method = "build_#{association_name}" - if respond_to?(method) - send(method, attributes.except(*UNASSIGNABLE_KEYS)) - else - raise ArgumentError, "Cannot build association #{association_name}. Are you trying to build a polymorphic one-to-one association?" - end + raise ArgumentError, "Cannot build association #{association_name}. Are you trying to build a polymorphic one-to-one association?" end - elsif (existing_record = send(association_name)) && existing_record.id.to_s == attributes['id'].to_s - assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) end end -- cgit v1.2.3 From ea7b5ff99e44aac0fc643e02dc4d046ab99ecdc7 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Mon, 28 Dec 2009 12:12:53 -0800 Subject: Use present rather than any --- activerecord/lib/active_record/relation.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index c7a74b7763..cb743358b2 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -25,7 +25,7 @@ module ActiveRecord select(r.send(:select_clauses).join(', ')). eager_load(r.eager_load_associations). preload(r.associations_to_preload). - from(r.send(:sources).any? ? r.send(:from_clauses) : nil) + from(r.send(:sources).present? ? r.send(:from_clauses) : nil) end alias :& :merge @@ -158,7 +158,7 @@ module ActiveRecord :conditions => where_clause, :limit => @relation.taken, :offset => @relation.skipped, - :from => (@relation.send(:from_clauses) if @relation.send(:sources).any?) + :from => (@relation.send(:from_clauses) if @relation.send(:sources).present?) }, ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations, nil)) end -- cgit v1.2.3 From 1b91f534ce91ff5d0a4d39d96a8f19b58022d403 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Mon, 28 Dec 2009 14:06:22 -0800 Subject: Fix uniqueness validation: with_exclusive_scope is not public --- activerecord/lib/active_record/validations/uniqueness.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index b3a34501dc..ffbe1b5c40 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -22,7 +22,7 @@ module ActiveRecord params << record.send(:id) end - finder_class.with_exclusive_scope do + finder_class.send(:with_exclusive_scope) do if finder_class.exists?([sql, *params]) record.errors.add(attribute, :taken, :default => options[:message], :value => value) end -- cgit v1.2.3 From 08633bae5e4f05e913ec5d5d2483bfd6c07c7375 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 29 Dec 2009 04:28:43 +0530 Subject: Migrate all the calculation methods to Relation --- activerecord/lib/active_record/associations.rb | 11 +- .../associations/association_collection.rb | 2 +- activerecord/lib/active_record/base.rb | 4 + activerecord/lib/active_record/calculations.rb | 200 ++++++--------------- activerecord/lib/active_record/relation.rb | 7 +- .../lib/active_record/relational_calculations.rb | 147 +++++++++++++-- 6 files changed, 203 insertions(+), 168 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 0b248a6c55..f0bad6c3ba 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1466,11 +1466,10 @@ module ActiveRecord end def find_with_associations(options = {}, join_dependency = nil) - catch :invalid_query do - join_dependency ||= JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins]) - rows = select_all_rows(options, join_dependency) - return join_dependency.instantiate(rows) - end + join_dependency ||= JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins]) + rows = select_all_rows(options, join_dependency) + join_dependency.instantiate(rows) + rescue ThrowResult [] end @@ -1733,7 +1732,7 @@ module ActiveRecord def construct_arel_limited_ids_condition(options, join_dependency) if (ids_array = select_limited_ids_array(options, join_dependency)).empty? - throw :invalid_query + raise ThrowResult else Arel::Predicates::In.new( Arel::SqlLiteral.new("#{connection.quote_table_name table_name}.#{primary_key}"), diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 56b2a90138..b2b3a9789c 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -177,7 +177,7 @@ module ActiveRecord if @reflection.options[:counter_sql] @reflection.klass.count_by_sql(@counter_sql) else - column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args) + column_name, options = @reflection.klass.scoped.send(:construct_count_options_from_args, *args) if @reflection.options[:uniq] # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL. column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" if column_name == :all diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 767109474d..53f0a920a3 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -69,6 +69,10 @@ module ActiveRecord #:nodoc: class StatementInvalid < ActiveRecordError end + # Raised when SQL statement is invalid and the application gets a blank result. + class ThrowResult < ActiveRecordError + end + # Parent class for all specific exceptions which wrap database driver exceptions # provides access to the original exception also. class WrappedDatabaseException < StatementInvalid diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index 59811c40a8..d51d9f2159 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -44,7 +44,26 @@ module ActiveRecord # # Note: Person.count(:all) will not work because it will use :all as the condition. Use Person.count instead. def count(*args) - calculate(:count, *construct_count_options_from_args(*args)) + case args.size + when 0 + construct_calculation_arel.count + when 1 + if args[0].is_a?(Hash) + options = args[0] + distinct = options.has_key?(:distinct) ? options.delete(:distinct) : false + construct_calculation_arel(options).count(options[:select], :distinct => distinct) + else + construct_calculation_arel.count(args[0]) + end + when 2 + column_name, options = args + distinct = options.has_key?(:distinct) ? options.delete(:distinct) : false + construct_calculation_arel(options).count(column_name, :distinct => distinct) + else + raise ArgumentError, "Unexpected parameters passed to count(): #{args.inspect}" + end + rescue ThrowResult + 0 end # Calculates the average value on a given column. The value is returned as @@ -122,168 +141,63 @@ module ActiveRecord # Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) # Selects the minimum age for any family without any minors # Person.sum("2 * age") def calculate(operation, column_name, options = {}) - validate_calculation_options(operation, options) - operation = operation.to_s.downcase - - scope = scope(:find) + construct_calculation_arel(options).calculate(operation, column_name, options.slice(:distinct)) + rescue ThrowResult + 0 + end - merged_includes = merge_includes(scope ? scope[:include] : [], options[:include]) + private + def validate_calculation_options(options = {}) + options.assert_valid_keys(CALCULATIONS_OPTIONS) + end - if operation == "count" - if merged_includes.any? - distinct = true - column_name = options[:select] || primary_key - end + def construct_calculation_arel(options = {}) + validate_calculation_options(options) + options = options.except(:distinct) - distinct = nil if column_name.to_s =~ /\s*DISTINCT\s+/i - distinct ||= options[:distinct] - else - distinct = nil - end + scope = scope(:find) + includes = merge_includes(scope ? scope[:include] : [], options[:include]) - catch :invalid_query do - relation = if merged_includes.any? - join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, construct_join(options[:joins], scope)) - construct_finder_arel_with_included_associations(options, join_dependency) + if includes.any? + join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, includes, construct_join(options[:joins], scope)) + construct_calculation_arel_with_included_associations(options, join_dependency) else - relation = arel_table(options[:from]). + arel_table. joins(construct_join(options[:joins], scope)). + from((scope && scope[:from]) || options[:from]). where(construct_conditions(options[:conditions], scope)). order(options[:order]). limit(options[:limit]). - offset(options[:offset]) - end - if options[:group] - return execute_grouped_calculation(operation, column_name, options, relation) - else - return execute_simple_calculation(operation, column_name, options.merge(:distinct => distinct), relation) + offset(options[:offset]). + group(options[:group]). + having(options[:having]). + select(options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))) end end - 0 - end - - def execute_simple_calculation(operation, column_name, options, relation) #:nodoc: - column = if column_names.include?(column_name.to_s) - Arel::Attribute.new(arel_table(options[:from] || table_name), - options[:select] || column_name) - else - Arel::SqlLiteral.new(options[:select] || - (column_name == :all ? "*" : column_name.to_s)) - end - - relation = relation.select(operation == 'count' ? column.count(options[:distinct]) : column.send(operation)) - - type_cast_calculated_value(connection.select_value(relation.to_sql), column_for(column_name), operation) - end - def execute_grouped_calculation(operation, column_name, options, relation) #:nodoc: - group_attr = options[:group].to_s - association = reflect_on_association(group_attr.to_sym) - associated = association && association.macro == :belongs_to # only count belongs_to associations - group_field = associated ? association.primary_key_name : group_attr - group_alias = column_alias_for(group_field) - group_column = column_for group_field + def construct_calculation_arel_with_included_associations(options, join_dependency) + scope = scope(:find) - options[:group] = connection.adapter_name == 'FrontBase' ? group_alias : group_field + relation = arel_table - aggregate_alias = column_alias_for(operation, column_name) - - options[:select] = (operation == 'count' && column_name == :all) ? - "COUNT(*) AS count_all" : - Arel::Attribute.new(arel_table, column_name).send(operation).as(aggregate_alias).to_sql - - options[:select] << ", #{group_field} AS #{group_alias}" - - relation = relation.select(options[:select]).group(options[:group]).having(options[:having]) - - calculated_data = connection.select_all(relation.to_sql) - - if association - key_ids = calculated_data.collect { |row| row[group_alias] } - key_records = association.klass.base_class.find(key_ids) - key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) } - end - - calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row| - key = type_cast_calculated_value(row[group_alias], group_column) - key = key_records[key] if associated - value = row[aggregate_alias] - all[key] = type_cast_calculated_value(value, column_for(column_name), operation) - all - end - end - - protected - def construct_count_options_from_args(*args) - options = {} - column_name = :all - - # We need to handle - # count() - # count(:column_name=:all) - # count(options={}) - # count(column_name=:all, options={}) - # selects specified by scopes - case args.size - when 0 - column_name = scope(:find)[:select] if scope(:find) - when 1 - if args[0].is_a?(Hash) - column_name = scope(:find)[:select] if scope(:find) - options = args[0] - else - column_name = args[0] - end - when 2 - column_name, options = args - else - raise ArgumentError, "Unexpected parameters passed to count(): #{args.inspect}" + for association in join_dependency.join_associations + relation = association.join_relation(relation) end - [column_name || :all, options] - end - - private - def validate_calculation_options(operation, options = {}) - options.assert_valid_keys(CALCULATIONS_OPTIONS) - end + relation = relation.joins(construct_join(options[:joins], scope)). + select(column_aliases(join_dependency)). + group(options[:group]). + having(options[:having]). + order(options[:order]). + where(construct_conditions(options[:conditions], scope)). + from((scope && scope[:from]) || options[:from]) - # Converts the given keys to the value that the database adapter returns as - # a usable column name: - # - # column_alias_for("users.id") # => "users_id" - # column_alias_for("sum(id)") # => "sum_id" - # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id" - # column_alias_for("count(*)") # => "count_all" - # column_alias_for("count", "id") # => "count_id" - def column_alias_for(*keys) - table_name = keys.join(' ') - table_name.downcase! - table_name.gsub!(/\*/, 'all') - table_name.gsub!(/\W+/, ' ') - table_name.strip! - table_name.gsub!(/ +/, '_') + relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency)) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit]) + relation = relation.limit(construct_limit(options[:limit], scope)) if using_limitable_reflections?(join_dependency.reflections) - connection.table_alias_for(table_name) + relation end - def column_for(field) - field_name = field.to_s.split('.').last - columns.detect { |c| c.name.to_s == field_name } - end - - def type_cast_calculated_value(value, column, operation = nil) - case operation - when 'count' then value.to_i - when 'sum' then type_cast_using_column(value || '0', column) - when 'average' then value && (value.is_a?(Fixnum) ? value.to_f : value).to_d - else type_cast_using_column(value, column) - end - end - - def type_cast_using_column(value, column) - column ? column.type_cast(value) : value - end end end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index cb743358b2..e495aa80db 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -149,8 +149,8 @@ module ActiveRecord return @records if loaded? @records = if @eager_load_associations.any? - catch :invalid_query do - return @klass.send(:find_with_associations, { + begin + @klass.send(:find_with_associations, { :select => @relation.send(:select_clauses).join(', '), :joins => @relation.joins(relation), :group => @relation.send(:group_clauses).join(', '), @@ -161,8 +161,9 @@ module ActiveRecord :from => (@relation.send(:from_clauses) if @relation.send(:sources).present?) }, ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations, nil)) + rescue ThrowResult + [] end - [] else @klass.find_by_sql(@relation.to_sql) end diff --git a/activerecord/lib/active_record/relational_calculations.rb b/activerecord/lib/active_record/relational_calculations.rb index 10eb992167..d77624c7bf 100644 --- a/activerecord/lib/active_record/relational_calculations.rb +++ b/activerecord/lib/active_record/relational_calculations.rb @@ -2,39 +2,119 @@ module ActiveRecord module RelationalCalculations def count(*args) - column_name, options = construct_count_options_from_args(*args) - distinct = options[:distinct] ? true : false + calculate(:count, *construct_count_options_from_args(*args)) + end + + def average(column_name) + calculate(:average, column_name) + end + + def minimum(column_name) + calculate(:minimum, column_name) + end + + def maximum(column_name) + calculate(:maximum, column_name) + end + + def sum(column_name) + calculate(:sum, column_name) + end + + def calculate(operation, column_name, options = {}) + operation = operation.to_s.downcase + + if operation == "count" + joins = @relation.joins(relation) + if joins.present? && joins =~ /LEFT OUTER/i + distinct = true + column_name = @klass.primary_key if column_name == :all + end + distinct = nil if column_name.to_s =~ /\s*DISTINCT\s+/i + distinct ||= options[:distinct] + else + distinct = nil + end + + distinct = options[:distinct] || distinct + column_name = :all if column_name.blank? && operation == "count" + + if @relation.send(:groupings).any? + return execute_grouped_calculation(operation, column_name) + else + return execute_simple_calculation(operation, column_name, distinct) + end + rescue ThrowResult + 0 + end + + private + + def execute_simple_calculation(operation, column_name, distinct) #:nodoc: column = if @klass.column_names.include?(column_name.to_s) - Arel::Attribute.new(@relation.table, column_name) + Arel::Attribute.new(@klass.arel_table, column_name) else Arel::SqlLiteral.new(column_name == :all ? "*" : column_name.to_s) end - relation = select(column.count(distinct)) - @klass.connection.select_value(relation.to_sql).to_i + relation = select(operation == 'count' ? column.count(distinct) : column.send(operation)) + type_cast_calculated_value(@klass.connection.select_value(relation.to_sql), column_for(column_name), operation) end - private + def execute_grouped_calculation(operation, column_name) #:nodoc: + group_attr = @relation.send(:groupings).first.value + association = @klass.reflect_on_association(group_attr.to_sym) + associated = association && association.macro == :belongs_to # only count belongs_to associations + group_field = associated ? association.primary_key_name : group_attr + group_alias = column_alias_for(group_field) + group_column = column_for(group_field) + + group = @klass.connection.adapter_name == 'FrontBase' ? group_alias : group_field + + aggregate_alias = column_alias_for(operation, column_name) + + select_statement = if operation == 'count' && column_name == :all + "COUNT(*) AS count_all" + else + Arel::Attribute.new(@klass.arel_table, column_name).send(operation).as(aggregate_alias).to_sql + end + + select_statement << ", #{group_field} AS #{group_alias}" + + relation = select(select_statement).group(group) + + calculated_data = @klass.connection.select_all(relation.to_sql) + + if association + key_ids = calculated_data.collect { |row| row[group_alias] } + key_records = association.klass.base_class.find(key_ids) + key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) } + end + + calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row| + key = type_cast_calculated_value(row[group_alias], group_column) + key = key_records[key] if associated + value = row[aggregate_alias] + all[key] = type_cast_calculated_value(value, column_for(column_name), operation) + all + end + end def construct_count_options_from_args(*args) options = {} column_name = :all - # We need to handle - # count() - # count(:column_name=:all) - # count(options={}) - # count(column_name=:all, options={}) - # selects specified by scopes - + # Handles count(), count(:column), count(:distinct => true), count(:column, :distinct => true) # TODO : relation.projections only works when .select() was last in the chain. Fix it! case args.size when 0 - column_name = @relation.send(:select_clauses).join(', ') if @relation.respond_to?(:projections) && @relation.projections.present? + select = @relation.send(:select_clauses).join(', ') if @relation.respond_to?(:projections) && @relation.projections.present? + column_name = select if select !~ /(,|\*)/ when 1 if args[0].is_a?(Hash) - column_name = @relation.send(:select_clauses).join(', ') if @relation.respond_to?(:projections) && @relation.projections.present? + select = @relation.send(:select_clauses).join(', ') if @relation.respond_to?(:projections) && @relation.projections.present? + column_name = select if select !~ /(,|\*)/ options = args[0] else column_name = args[0] @@ -48,5 +128,42 @@ module ActiveRecord [column_name || :all, options] end + # Converts the given keys to the value that the database adapter returns as + # a usable column name: + # + # column_alias_for("users.id") # => "users_id" + # column_alias_for("sum(id)") # => "sum_id" + # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id" + # column_alias_for("count(*)") # => "count_all" + # column_alias_for("count", "id") # => "count_id" + def column_alias_for(*keys) + table_name = keys.join(' ') + table_name.downcase! + table_name.gsub!(/\*/, 'all') + table_name.gsub!(/\W+/, ' ') + table_name.strip! + table_name.gsub!(/ +/, '_') + + @klass.connection.table_alias_for(table_name) + end + + def column_for(field) + field_name = field.to_s.split('.').last + @klass.columns.detect { |c| c.name.to_s == field_name } + end + + def type_cast_calculated_value(value, column, operation = nil) + case operation + when 'count' then value.to_i + when 'sum' then type_cast_using_column(value || '0', column) + when 'average' then value && (value.is_a?(Fixnum) ? value.to_f : value).to_d + else type_cast_using_column(value, column) + end + end + + def type_cast_using_column(value, column) + column ? column.type_cast(value) : value + end + end end -- cgit v1.2.3 From 6d390671f639b3314437054df0d393cef10493bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 29 Dec 2009 01:15:39 +0100 Subject: Move ActiveRecord callbacks implementation to ActiveModel and make use of it. Signed-off-by: Yehuda Katz --- activerecord/lib/active_record/callbacks.rb | 59 +++-------------------------- 1 file changed, 5 insertions(+), 54 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index b25893a1c3..e1d772bd95 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -1,5 +1,3 @@ -require 'observer' - module ActiveRecord # Callbacks are hooks into the lifecycle of an Active Record object that allow you to trigger logic # before or after an alteration of the object state. This can be used to make sure that associated and @@ -210,7 +208,6 @@ module ActiveRecord # instead of quietly returning +false+. module Callbacks extend ActiveSupport::Concern - include ActiveSupport::Callbacks CALLBACKS = [ :after_initialize, :after_find, :before_validation, :after_validation, @@ -224,60 +221,14 @@ module ActiveRecord alias_method_chain method, :callbacks end - define_callbacks :initialize, :find, :save, :create, :update, :destroy, - :validation, :terminator => "result == false", :scope => [:kind, :name] + extend ActiveModel::Callbacks + + define_model_callbacks :initialize, :find, :only => :after + define_model_callbacks :save, :create, :update, :destroy + define_model_callbacks :validation, :only => [:before, :after] end module ClassMethods - def after_initialize(*args, &block) - options = args.extract_options! - options[:prepend] = true - set_callback(:initialize, :after, *(args << options), &block) - end - - def after_find(*args, &block) - options = args.extract_options! - options[:prepend] = true - set_callback(:find, :after, *(args << options), &block) - end - - [:save, :create, :update, :destroy].each do |callback| - module_eval <<-CALLBACKS, __FILE__, __LINE__ - def before_#{callback}(*args, &block) - set_callback(:#{callback}, :before, *args, &block) - end - - def around_#{callback}(*args, &block) - set_callback(:#{callback}, :around, *args, &block) - end - - def after_#{callback}(*args, &block) - options = args.extract_options! - options[:prepend] = true - options[:if] = Array(options[:if]) << "!halted && value != false" - set_callback(:#{callback}, :after, *(args << options), &block) - end - CALLBACKS - end - - def before_validation(*args, &block) - options = args.extract_options! - if options[:on] - options[:if] = Array(options[:if]) - options[:if] << "@_on_validate == :#{options[:on]}" - end - set_callback(:validation, :before, *(args << options), &block) - end - - def after_validation(*args, &block) - options = args.extract_options! - options[:if] = Array(options[:if]) - options[:if] << "!halted" - options[:if] << "@_on_validate == :#{options[:on]}" if options[:on] - options[:prepend] = true - set_callback(:validation, :after, *(args << options), &block) - end - def method_added(meth) super if CALLBACKS.include?(meth.to_sym) -- cgit v1.2.3 From 49c800b6bdf10af4e3f07eee33c4e6d23e7a946d Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Mon, 28 Dec 2009 17:37:18 -0800 Subject: Move the ActiveRecord related rake tasks into the AR gem. --- activerecord/lib/active_record/rails.rb | 4 + .../lib/active_record/rails/databases.rake | 469 +++++++++++++++++++++ 2 files changed, 473 insertions(+) create mode 100644 activerecord/lib/active_record/rails/databases.rake (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/rails.rb b/activerecord/lib/active_record/rails.rb index 8b22f869bc..e7de699974 100644 --- a/activerecord/lib/active_record/rails.rb +++ b/activerecord/lib/active_record/rails.rb @@ -11,6 +11,10 @@ module ActiveRecord config.action_controller.include "ActiveRecord::ControllerRuntime" + rake_tasks do + load "active_record/rails/databases.rake" + end + initializer "active_record.set_configs" do |app| app.config.active_record.each do |k,v| ActiveRecord::Base.send "#{k}=", v diff --git a/activerecord/lib/active_record/rails/databases.rake b/activerecord/lib/active_record/rails/databases.rake new file mode 100644 index 0000000000..a35a6c156b --- /dev/null +++ b/activerecord/lib/active_record/rails/databases.rake @@ -0,0 +1,469 @@ +namespace :db do + task :load_config => :rails_env do + require 'active_record' + ActiveRecord::Base.configurations = Rails::Configuration.new.database_configuration + end + + namespace :create do + desc 'Create all the local databases defined in config/database.yml' + task :all => :load_config do + ActiveRecord::Base.configurations.each_value do |config| + # Skip entries that don't have a database key, such as the first entry here: + # + # defaults: &defaults + # adapter: mysql + # username: root + # password: + # host: localhost + # + # development: + # database: blog_development + # <<: *defaults + next unless config['database'] + # Only connect to local databases + local_database?(config) { create_database(config) } + end + end + end + + desc 'Create the database defined in config/database.yml for the current RAILS_ENV' + task :create => :load_config do + create_database(ActiveRecord::Base.configurations[RAILS_ENV]) + end + + def create_database(config) + begin + if config['adapter'] =~ /sqlite/ + if File.exist?(config['database']) + $stderr.puts "#{config['database']} already exists" + else + begin + # Create the SQLite database + ActiveRecord::Base.establish_connection(config) + ActiveRecord::Base.connection + rescue + $stderr.puts $!, *($!.backtrace) + $stderr.puts "Couldn't create database for #{config.inspect}" + end + end + return # Skip the else clause of begin/rescue + else + ActiveRecord::Base.establish_connection(config) + ActiveRecord::Base.connection + end + rescue + case config['adapter'] + when 'mysql' + @charset = ENV['CHARSET'] || 'utf8' + @collation = ENV['COLLATION'] || 'utf8_unicode_ci' + creation_options = {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)} + begin + ActiveRecord::Base.establish_connection(config.merge('database' => nil)) + ActiveRecord::Base.connection.create_database(config['database'], creation_options) + ActiveRecord::Base.establish_connection(config) + rescue Mysql::Error => sqlerr + if sqlerr.errno == Mysql::Error::ER_ACCESS_DENIED_ERROR + print "#{sqlerr.error}. \nPlease provide the root password for your mysql installation\n>" + root_password = $stdin.gets.strip + grant_statement = "GRANT ALL PRIVILEGES ON #{config['database']}.* " \ + "TO '#{config['username']}'@'localhost' " \ + "IDENTIFIED BY '#{config['password']}' WITH GRANT OPTION;" + ActiveRecord::Base.establish_connection(config.merge( + 'database' => nil, 'username' => 'root', 'password' => root_password)) + ActiveRecord::Base.connection.create_database(config['database'], creation_options) + ActiveRecord::Base.connection.execute grant_statement + ActiveRecord::Base.establish_connection(config) + else + $stderr.puts sqlerr.error + $stderr.puts "Couldn't create database for #{config.inspect}, charset: #{config['charset'] || @charset}, collation: #{config['collation'] || @collation}" + $stderr.puts "(if you set the charset manually, make sure you have a matching collation)" if config['charset'] + end + end + when 'postgresql' + @encoding = config[:encoding] || ENV['CHARSET'] || 'utf8' + begin + ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public')) + ActiveRecord::Base.connection.create_database(config['database'], config.merge('encoding' => @encoding)) + ActiveRecord::Base.establish_connection(config) + rescue + $stderr.puts $!, *($!.backtrace) + $stderr.puts "Couldn't create database for #{config.inspect}" + end + end + else + $stderr.puts "#{config['database']} already exists" + end + end + + namespace :drop do + desc 'Drops all the local databases defined in config/database.yml' + task :all => :load_config do + ActiveRecord::Base.configurations.each_value do |config| + # Skip entries that don't have a database key + next unless config['database'] + begin + # Only connect to local databases + local_database?(config) { drop_database(config) } + rescue Exception => e + puts "Couldn't drop #{config['database']} : #{e.inspect}" + end + end + end + end + + desc 'Drops the database for the current RAILS_ENV' + task :drop => :load_config do + config = ActiveRecord::Base.configurations[RAILS_ENV || 'development'] + begin + drop_database(config) + rescue Exception => e + puts "Couldn't drop #{config['database']} : #{e.inspect}" + end + end + + def local_database?(config, &block) + if %w( 127.0.0.1 localhost ).include?(config['host']) || config['host'].blank? + yield + else + puts "This task only modifies local databases. #{config['database']} is on a remote host." + end + end + + + desc "Migrate the database through scripts in db/migrate and update db/schema.rb by invoking db:schema:dump. Target specific version with VERSION=x. Turn off output with VERBOSE=false." + task :migrate => :environment do + ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true + ActiveRecord::Migrator.migrate("db/migrate/", ENV["VERSION"] ? ENV["VERSION"].to_i : nil) + Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby + end + + namespace :migrate do + desc 'Rollbacks the database one migration and re migrate up. If you want to rollback more than one step, define STEP=x. Target specific version with VERSION=x.' + task :redo => :environment do + if ENV["VERSION"] + Rake::Task["db:migrate:down"].invoke + Rake::Task["db:migrate:up"].invoke + else + Rake::Task["db:rollback"].invoke + Rake::Task["db:migrate"].invoke + end + end + + desc 'Resets your database using your migrations for the current environment' + task :reset => ["db:drop", "db:create", "db:migrate"] + + desc 'Runs the "up" for a given migration VERSION.' + task :up => :environment do + version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil + raise "VERSION is required" unless version + ActiveRecord::Migrator.run(:up, "db/migrate/", version) + Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby + end + + desc 'Runs the "down" for a given migration VERSION.' + task :down => :environment do + version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil + raise "VERSION is required" unless version + ActiveRecord::Migrator.run(:down, "db/migrate/", version) + Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby + end + end + + desc 'Rolls the schema back to the previous version. Specify the number of steps with STEP=n' + task :rollback => :environment do + step = ENV['STEP'] ? ENV['STEP'].to_i : 1 + ActiveRecord::Migrator.rollback('db/migrate/', step) + Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby + end + + desc 'Pushes the schema to the next version. Specify the number of steps with STEP=n' + task :forward => :environment do + step = ENV['STEP'] ? ENV['STEP'].to_i : 1 + ActiveRecord::Migrator.forward('db/migrate/', step) + Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby + end + + desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.' + task :reset => [ 'db:drop', 'db:setup' ] + + desc "Retrieves the charset for the current environment's database" + task :charset => :environment do + config = ActiveRecord::Base.configurations[RAILS_ENV || 'development'] + case config['adapter'] + when 'mysql' + ActiveRecord::Base.establish_connection(config) + puts ActiveRecord::Base.connection.charset + when 'postgresql' + ActiveRecord::Base.establish_connection(config) + puts ActiveRecord::Base.connection.encoding + else + puts 'sorry, your database adapter is not supported yet, feel free to submit a patch' + end + end + + desc "Retrieves the collation for the current environment's database" + task :collation => :environment do + config = ActiveRecord::Base.configurations[RAILS_ENV || 'development'] + case config['adapter'] + when 'mysql' + ActiveRecord::Base.establish_connection(config) + puts ActiveRecord::Base.connection.collation + else + puts 'sorry, your database adapter is not supported yet, feel free to submit a patch' + end + end + + desc "Retrieves the current schema version number" + task :version => :environment do + puts "Current version: #{ActiveRecord::Migrator.current_version}" + end + + desc "Raises an error if there are pending migrations" + task :abort_if_pending_migrations => :environment do + if defined? ActiveRecord + pending_migrations = ActiveRecord::Migrator.new(:up, 'db/migrate').pending_migrations + + if pending_migrations.any? + puts "You have #{pending_migrations.size} pending migrations:" + pending_migrations.each do |pending_migration| + puts ' %4d %s' % [pending_migration.version, pending_migration.name] + end + abort %{Run "rake db:migrate" to update your database then try again.} + end + end + end + + desc 'Create the database, load the schema, and initialize with the seed data' + task :setup => [ 'db:create', 'db:schema:load', 'db:seed' ] + + desc 'Load the seed data from db/seeds.rb' + task :seed => :environment do + seed_file = File.join(Rails.root, 'db', 'seeds.rb') + load(seed_file) if File.exist?(seed_file) + end + + namespace :fixtures do + desc "Load fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures." + task :load => :environment do + require 'active_record/fixtures' + ActiveRecord::Base.establish_connection(Rails.env) + base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures') + fixtures_dir = ENV['FIXTURES_DIR'] ? File.join(base_dir, ENV['FIXTURES_DIR']) : base_dir + + (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/).map {|f| File.join(fixtures_dir, f) } : Dir.glob(File.join(fixtures_dir, '*.{yml,csv}'))).each do |fixture_file| + Fixtures.create_fixtures(File.dirname(fixture_file), File.basename(fixture_file, '.*')) + end + end + + desc "Search for a fixture given a LABEL or ID. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures." + task :identify => :environment do + require "active_record/fixtures" + + label, id = ENV["LABEL"], ENV["ID"] + raise "LABEL or ID required" if label.blank? && id.blank? + + puts %Q(The fixture ID for "#{label}" is #{Fixtures.identify(label)}.) if label + + base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures') + Dir["#{base_dir}/**/*.yml"].each do |file| + if data = YAML::load(ERB.new(IO.read(file)).result) + data.keys.each do |key| + key_id = Fixtures.identify(key) + + if key == label || key_id == id.to_i + puts "#{file}: #{key} (#{key_id})" + end + end + end + end + end + end + + namespace :schema do + desc "Create a db/schema.rb file that can be portably used against any DB supported by AR" + task :dump => :environment do + require 'active_record/schema_dumper' + File.open(ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb", "w") do |file| + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file) + end + Rake::Task["db:schema:dump"].reenable + end + + desc "Load a schema.rb file into the database" + task :load => :environment do + file = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb" + if File.exists?(file) + load(file) + else + abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to prevent active_record from loading: config.frameworks -= [ :active_record ]} + end + end + end + + namespace :structure do + desc "Dump the database structure to a SQL file" + task :dump => :environment do + abcs = ActiveRecord::Base.configurations + case abcs[RAILS_ENV]["adapter"] + 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" + ENV['PGHOST'] = abcs[RAILS_ENV]["host"] if abcs[RAILS_ENV]["host"] + ENV['PGPORT'] = abcs[RAILS_ENV]["port"].to_s if abcs[RAILS_ENV]["port"] + ENV['PGPASSWORD'] = abcs[RAILS_ENV]["password"].to_s if abcs[RAILS_ENV]["password"] + search_path = abcs[RAILS_ENV]["schema_search_path"] + unless search_path.blank? + search_path = search_path.split(",").map{|search_path| "--schema=#{search_path.strip}" }.join(" ") + end + `pg_dump -i -U "#{abcs[RAILS_ENV]["username"]}" -s -x -O -f db/#{RAILS_ENV}_structure.sql #{search_path} #{abcs[RAILS_ENV]["database"]}` + raise "Error dumping database" if $?.exitstatus == 1 + when "sqlite", "sqlite3" + dbfile = abcs[RAILS_ENV]["database"] || abcs[RAILS_ENV]["dbfile"] + `#{abcs[RAILS_ENV]["adapter"]} #{dbfile} .schema > db/#{RAILS_ENV}_structure.sql` + when "sqlserver" + `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /f db\\#{RAILS_ENV}_structure.sql /q /A /r` + `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /F db\ /q /A /r` + when "firebird" + set_firebird_env(abcs[RAILS_ENV]) + db_string = firebird_db_string(abcs[RAILS_ENV]) + sh "isql -a #{db_string} > #{Rails.root}/db/#{RAILS_ENV}_structure.sql" + else + raise "Task not supported by '#{abcs["test"]["adapter"]}'" + end + + if ActiveRecord::Base.connection.supports_migrations? + File.open("#{Rails.root}/db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information } + end + end + end + + namespace :test do + desc "Recreate the test database from the current schema.rb" + task :load => 'db:test:purge' do + ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test']) + ActiveRecord::Schema.verbose = false + Rake::Task["db:schema:load"].invoke + end + + desc "Recreate the test database from the current environment's database schema" + task :clone => %w(db:schema:dump db:test:load) + + desc "Recreate the test databases from the development structure" + task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do + abcs = ActiveRecord::Base.configurations + case abcs["test"]["adapter"] + 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| + ActiveRecord::Base.connection.execute(table) + end + when "postgresql" + ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"] + ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"] + ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"] + `psql -U "#{abcs["test"]["username"]}" -f #{Rails.root}/db/#{RAILS_ENV}_structure.sql #{abcs["test"]["database"]}` + when "sqlite", "sqlite3" + dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"] + `#{abcs["test"]["adapter"]} #{dbfile} < #{Rails.root}/db/#{RAILS_ENV}_structure.sql` + when "sqlserver" + `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql` + when "oci", "oracle" + ActiveRecord::Base.establish_connection(:test) + IO.readlines("#{Rails.root}/db/#{RAILS_ENV}_structure.sql").join.split(";\n\n").each do |ddl| + ActiveRecord::Base.connection.execute(ddl) + end + when "firebird" + set_firebird_env(abcs["test"]) + db_string = firebird_db_string(abcs["test"]) + sh "isql -i #{Rails.root}/db/#{RAILS_ENV}_structure.sql #{db_string}" + else + raise "Task not supported by '#{abcs["test"]["adapter"]}'" + end + end + + desc "Empty the test database" + task :purge => :environment do + abcs = ActiveRecord::Base.configurations + case abcs["test"]["adapter"] + when "mysql" + ActiveRecord::Base.establish_connection(:test) + ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"], abcs["test"]) + when "postgresql" + ActiveRecord::Base.clear_active_connections! + drop_database(abcs['test']) + create_database(abcs['test']) + when "sqlite","sqlite3" + dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"] + File.delete(dbfile) if File.exist?(dbfile) + when "sqlserver" + dropfkscript = "#{abcs["test"]["host"]}.#{abcs["test"]["database"]}.DP1".gsub(/\\/,'-') + `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{dropfkscript}` + `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql` + when "oci", "oracle" + ActiveRecord::Base.establish_connection(:test) + ActiveRecord::Base.connection.structure_drop.split(";\n\n").each do |ddl| + ActiveRecord::Base.connection.execute(ddl) + end + when "firebird" + ActiveRecord::Base.establish_connection(:test) + ActiveRecord::Base.connection.recreate_database! + else + raise "Task not supported by '#{abcs["test"]["adapter"]}'" + end + end + + desc 'Check for pending migrations and load the test schema' + task :prepare => 'db:abort_if_pending_migrations' do + if defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank? + Rake::Task[{ :sql => "db:test:clone_structure", :ruby => "db:test:load" }[ActiveRecord::Base.schema_format]].invoke + end + end + end + + namespace :sessions do + desc "Creates a sessions migration for use with ActiveRecord::SessionStore" + task :create => :environment do + raise "Task unavailable to this database (no migration support)" unless ActiveRecord::Base.connection.supports_migrations? + require 'rails/generators' + require 'rails/generators/rails/session_migration/session_migration_generator' + Rails::Generators::SessionMigrationGenerator.start [ ENV["MIGRATION"] || "add_sessions_table" ] + end + + desc "Clear the sessions table" + task :clear => :environment do + ActiveRecord::Base.connection.execute "DELETE FROM #{session_table_name}" + end + end +end + +def drop_database(config) + case config['adapter'] + when 'mysql' + ActiveRecord::Base.establish_connection(config) + ActiveRecord::Base.connection.drop_database config['database'] + when /^sqlite/ + require 'pathname' + path = Pathname.new(config['database']) + file = path.absolute? ? path.to_s : File.join(Rails.root, path) + + FileUtils.rm(file) + when 'postgresql' + ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public')) + ActiveRecord::Base.connection.drop_database config['database'] + end +end + +def session_table_name + ActiveRecord::SessionStore::Session.table_name +end + +def set_firebird_env(config) + ENV["ISC_USER"] = config["username"].to_s if config["username"] + ENV["ISC_PASSWORD"] = config["password"].to_s if config["password"] +end + +def firebird_db_string(config) + FireRuby::Database.db_string_for(config.symbolize_keys) +end -- cgit v1.2.3 From 54b80c73611fe8eb19039381bf0322326f5e1b51 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 29 Dec 2009 11:36:40 +0530 Subject: Add Relation#delete_all --- activerecord/lib/active_record/relation.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index e495aa80db..a3d7be3afe 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -134,6 +134,7 @@ module ActiveRecord if [String, Hash, Array].include?(args.first.class) conditions = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first) + conditions = Arel::SqlLiteral.new(conditions) if conditions else conditions = args.first end @@ -223,6 +224,11 @@ module ActiveRecord reset end + def delete_all + @relation.delete + reset + end + def loaded? @loaded end -- cgit v1.2.3 From 13989ff8c690b9b9e5282bd4098666c909ea64d3 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 29 Dec 2009 11:45:25 +0530 Subject: Use relation#delete_all for Model.delete_all --- activerecord/lib/active_record/base.rb | 6 +----- activerecord/lib/active_record/relation.rb | 3 +-- 2 files changed, 2 insertions(+), 7 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 53f0a920a3..6715871bf9 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -939,11 +939,7 @@ module ActiveRecord #:nodoc: # Both calls delete the affected posts all at once with a single DELETE statement. If you need to destroy dependent # associations or call your before_* or +after_destroy+ callbacks, use the +destroy_all+ method instead. def delete_all(conditions = nil) - if conditions - arel_table.where(Arel::SqlLiteral.new(construct_conditions(conditions, scope(:find)))).delete - else - arel_table.delete - end + arel_table.where(construct_conditions(conditions, scope(:find))).delete_all end # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part. diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index a3d7be3afe..c6c63291c9 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -225,8 +225,7 @@ module ActiveRecord end def delete_all - @relation.delete - reset + @relation.delete.tap { reset } end def loaded? -- cgit v1.2.3 From f290e685f0497427b348a2fae760300429d5f0cd Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 29 Dec 2009 12:15:28 +0530 Subject: Add Relation#size and Relation#empty? --- activerecord/lib/active_record/relation.rb | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index c6c63291c9..52a8bea567 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -219,6 +219,14 @@ module ActiveRecord end end + def size + loaded? ? @records.length : count + end + + def empty? + loaded? ? @records.empty? : count.zero? + end + def destroy_all to_a.each {|object| object.destroy} reset -- cgit v1.2.3 From bc933d0fa1b3a89deaf3e7c74d7717eb8fc7152a Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 29 Dec 2009 12:27:40 +0530 Subject: Make sure Relation responds to dynamic finder methods --- activerecord/lib/active_record/relation.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 52a8bea567..d7cd78593f 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -143,7 +143,15 @@ module ActiveRecord end def respond_to?(method) - @relation.respond_to?(method) || Array.method_defined?(method) || super + return true if @relation.respond_to?(method) || Array.method_defined?(method) + + if match = DynamicFinderMatch.match(method) + return true if @klass.send(:all_attributes_exists?, match.attribute_names) + elsif match = DynamicScopeMatch.match(method) + return true if @klass.send(:all_attributes_exists?, match.attribute_names) + else + super + end end def to_a -- cgit v1.2.3 From 981f69639841759a558a0083cd507fa610d564ad Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 29 Dec 2009 12:31:08 +0530 Subject: Relation#respond_to? should take second argument for responding to private methods --- activerecord/lib/active_record/relation.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index d7cd78593f..b82a7d44f7 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -142,8 +142,8 @@ module ActiveRecord create_new_relation(@relation.where(conditions)) end - def respond_to?(method) - return true if @relation.respond_to?(method) || Array.method_defined?(method) + def respond_to?(method, include_private = false) + return true if @relation.respond_to?(method, include_private) || Array.method_defined?(method) if match = DynamicFinderMatch.match(method) return true if @klass.send(:all_attributes_exists?, match.attribute_names) -- cgit v1.2.3 From 61fa111a5502c767b4b2c376eb5f06eb3077f45f Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 29 Dec 2009 15:07:51 +0530 Subject: Refactor Relation#readonly using attr_writer --- activerecord/lib/active_record/relation.rb | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index b82a7d44f7..249e6475e7 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -5,9 +5,11 @@ module ActiveRecord attr_reader :relation, :klass, :associations_to_preload, :eager_load_associations include RelationalCalculations - def initialize(klass, relation, readonly = false, preload = [], eager_load = []) + + attr_writer :readonly + + def initialize(klass, relation, preload = [], eager_load = []) @klass, @relation = klass, relation - @readonly = readonly @associations_to_preload = preload @eager_load_associations = eager_load @loaded = false @@ -31,21 +33,24 @@ module ActiveRecord alias :& :merge def preload(*associations) - create_new_relation(@relation, @readonly, @associations_to_preload + Array.wrap(associations)) + create_new_relation(@relation, @associations_to_preload + Array.wrap(associations)) end def eager_load(*associations) - create_new_relation(@relation, @readonly, @associations_to_preload, @eager_load_associations + Array.wrap(associations)) + create_new_relation(@relation, @associations_to_preload, @eager_load_associations + Array.wrap(associations)) end def readonly(status = true) - status.nil? ? create_new_relation : create_new_relation(@relation, status) + relation = create_new_relation + relation.readonly = status + relation end def select(selects) if selects.present? - frozen = @relation.joins(relation).present? ? false : @readonly - create_new_relation(@relation.project(selects), frozen) + relation = create_new_relation(@relation.project(selects)) + relation.readonly = @relation.joins(relation).present? ? false : @readonly + relation else create_new_relation end @@ -126,7 +131,9 @@ module ActiveRecord @relation.join(join, join_type) end - create_new_relation(join_relation, true) + relation = create_new_relation(join_relation) + relation.readonly = true + relation end def where(*args) @@ -352,8 +359,10 @@ module ActiveRecord end end - def create_new_relation(relation = @relation, readonly = @readonly, preload = @associations_to_preload, eager_load = @eager_load_associations) - self.class.new(@klass, relation, readonly, preload, eager_load) + def create_new_relation(relation = @relation, preload = @associations_to_preload, eager_load = @eager_load_associations) + relation = self.class.new(@klass, relation, preload, eager_load) + relation.readonly = @readonly + relation end def where_clause(join_string = "\n\tAND ") -- cgit v1.2.3 From 1785e1b26e761cfad365869b5dc36ce8495cf2b5 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 29 Dec 2009 15:24:09 +0530 Subject: Rename Relation#create_new_relation to spawn and refactor preload/eagerload associations using attr_writer --- activerecord/lib/active_record/relation.rb | 80 ++++++++++++++++-------------- 1 file changed, 43 insertions(+), 37 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 249e6475e7..e40432bf6a 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -1,18 +1,18 @@ module ActiveRecord class Relation + include RelationalCalculations + delegate :to_sql, :to => :relation delegate :length, :collect, :map, :each, :all?, :to => :to_a - attr_reader :relation, :klass, :associations_to_preload, :eager_load_associations - - include RelationalCalculations - attr_writer :readonly + attr_reader :relation, :klass, :preload_associations, :eager_load_associations + attr_writer :readonly, :preload_associations, :eager_load_associations - def initialize(klass, relation, preload = [], eager_load = []) + def initialize(klass, relation) @klass, @relation = klass, relation - @associations_to_preload = preload - @eager_load_associations = eager_load - @loaded = false + @preload_associations = [] + @eager_load_associations = [] + @loaded, @readonly = false end def merge(r) @@ -26,42 +26,46 @@ module ActiveRecord offset(r.skipped). select(r.send(:select_clauses).join(', ')). eager_load(r.eager_load_associations). - preload(r.associations_to_preload). + preload(r.preload_associations). from(r.send(:sources).present? ? r.send(:from_clauses) : nil) end alias :& :merge def preload(*associations) - create_new_relation(@relation, @associations_to_preload + Array.wrap(associations)) + relation = spawn + relation.preload_associations += Array.wrap(associations) + relation end def eager_load(*associations) - create_new_relation(@relation, @associations_to_preload, @eager_load_associations + Array.wrap(associations)) + relation = spawn + relation.eager_load_associations += Array.wrap(associations) + relation end def readonly(status = true) - relation = create_new_relation + relation = spawn relation.readonly = status relation end def select(selects) if selects.present? - relation = create_new_relation(@relation.project(selects)) + relation = spawn(@relation.project(selects)) relation.readonly = @relation.joins(relation).present? ? false : @readonly relation else - create_new_relation + spawn end end def from(from) - from.present? ? create_new_relation(@relation.from(from)) : create_new_relation + from.present? ? spawn(@relation.from(from)) : spawn end def having(*args) - return create_new_relation if args.blank? + return spawn if args.blank? if [String, Hash, Array].include?(args.first.class) havings = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first) @@ -69,30 +73,30 @@ module ActiveRecord havings = args.first end - create_new_relation(@relation.having(havings)) + spawn(@relation.having(havings)) end def group(groups) - groups.present? ? create_new_relation(@relation.group(groups)) : create_new_relation + groups.present? ? spawn(@relation.group(groups)) : spawn end def order(orders) - orders.present? ? create_new_relation(@relation.order(orders)) : create_new_relation + orders.present? ? spawn(@relation.order(orders)) : spawn end def lock(locks = true) case locks when String - create_new_relation(@relation.lock(locks)) + spawn(@relation.lock(locks)) when TrueClass, NilClass - create_new_relation(@relation.lock) + spawn(@relation.lock) else - create_new_relation + spawn end end def reverse_order - relation = create_new_relation + relation = spawn relation.instance_variable_set(:@orders, nil) order_clause = @relation.send(:order_clauses).join(', ') @@ -104,19 +108,19 @@ module ActiveRecord end def limit(limits) - limits.present? ? create_new_relation(@relation.take(limits)) : create_new_relation + limits.present? ? spawn(@relation.take(limits)) : spawn end def offset(offsets) - offsets.present? ? create_new_relation(@relation.skip(offsets)) : create_new_relation + offsets.present? ? spawn(@relation.skip(offsets)) : spawn end def on(join) - create_new_relation(@relation.on(join)) + spawn(@relation.on(join)) end def joins(join, join_type = nil) - return create_new_relation if join.blank? + return spawn if join.blank? join_relation = case join when String @@ -131,13 +135,13 @@ module ActiveRecord @relation.join(join, join_type) end - relation = create_new_relation(join_relation) + relation = spawn(join_relation) relation.readonly = true relation end def where(*args) - return create_new_relation if args.blank? + return spawn if args.blank? if [String, Hash, Array].include?(args.first.class) conditions = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first) @@ -146,7 +150,7 @@ module ActiveRecord conditions = args.first end - create_new_relation(@relation.where(conditions)) + spawn(@relation.where(conditions)) end def respond_to?(method, include_private = false) @@ -184,7 +188,7 @@ module ActiveRecord @klass.find_by_sql(@relation.to_sql) end - @associations_to_preload.each {|associations| @klass.send(:preload_associations, @records, associations) } + @preload_associations.each {|associations| @klass.send(:preload_associations, @records, associations) } @records.each { |record| record.readonly! } if @readonly @loaded = true @@ -266,6 +270,14 @@ module ActiveRecord self end + def spawn(relation = @relation) + relation = self.class.new(@klass, relation) + relation.readonly = @readonly + relation.preload_associations = @preload_associations + relation.eager_load_associations = @eager_load_associations + relation + end + protected def method_missing(method, *args, &block) @@ -359,12 +371,6 @@ module ActiveRecord end end - def create_new_relation(relation = @relation, preload = @associations_to_preload, eager_load = @eager_load_associations) - relation = self.class.new(@klass, relation, preload, eager_load) - relation.readonly = @readonly - relation - end - def where_clause(join_string = "\n\tAND ") @relation.send(:where_clauses).join(join_string) end -- cgit v1.2.3 From 0a1ff1a14ffcc96204ba1d391207d13e68cda8b0 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 29 Dec 2009 15:28:40 +0530 Subject: Rewrite Relation#readonly, eager_load, preload using Object#tap --- activerecord/lib/active_record/relation.rb | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index e40432bf6a..72e7c234eb 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -33,21 +33,15 @@ module ActiveRecord alias :& :merge def preload(*associations) - relation = spawn - relation.preload_associations += Array.wrap(associations) - relation + spawn.tap {|r| r.preload_associations += Array.wrap(associations) } end def eager_load(*associations) - relation = spawn - relation.eager_load_associations += Array.wrap(associations) - relation + spawn.tap {|r| r.eager_load_associations += Array.wrap(associations) } end def readonly(status = true) - relation = spawn - relation.readonly = status - relation + spawn.tap {|r| r.readonly = status } end def select(selects) @@ -135,9 +129,7 @@ module ActiveRecord @relation.join(join, join_type) end - relation = spawn(join_relation) - relation.readonly = true - relation + spawn(join_relation) { |r| r.readonly = true } end def where(*args) -- cgit v1.2.3 From 3b8853eda45dfcae2a1e71b890668730e63bed02 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 29 Dec 2009 15:53:06 +0530 Subject: Replace Base#safe_to_array with Array.wrap --- activerecord/lib/active_record/base.rb | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 6715871bf9..07c5545171 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1645,7 +1645,7 @@ module ActiveRecord #:nodoc: # Merges includes so that the result is a valid +include+ def merge_includes(first, second) - (safe_to_array(first) + safe_to_array(second)).uniq + (Array.wrap(first) + Array.wrap(second)).uniq end def merge_joins(*joins) @@ -1657,7 +1657,7 @@ module ActiveRecord #:nodoc: end joins.flatten.map{|j| j.strip}.uniq else - joins.collect{|j| safe_to_array(j)}.flatten.uniq + joins.collect{|j| Array.wrap(j)}.flatten.uniq end end @@ -1674,18 +1674,6 @@ module ActiveRecord #:nodoc: }.join(" ") end - # Object#to_a is deprecated, though it does have the desired behavior - def safe_to_array(o) - case o - when NilClass - [] - when Array - o - else - [o] - end - end - def array_of_strings?(o) o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)} end -- cgit v1.2.3 From 0dea509817a17fd4b64196cb83acbcef4818cb54 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 29 Dec 2009 15:54:40 +0530 Subject: Oops, add the missing #tap call --- activerecord/lib/active_record/relation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 72e7c234eb..64261db809 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -129,7 +129,7 @@ module ActiveRecord @relation.join(join, join_type) end - spawn(join_relation) { |r| r.readonly = true } + spawn(join_relation).tap { |r| r.readonly = true } end def where(*args) -- cgit v1.2.3 From 00cd3789f6d53163229669ad82a5d87fcdcb49ba Mon Sep 17 00:00:00 2001 From: Emilio Tagua Date: Wed, 30 Dec 2009 01:12:38 +0530 Subject: Relation#count should look for projections in chained relations and perform the count on the given column Signed-off-by: Pratik Naik --- .../lib/active_record/relational_calculations.rb | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relational_calculations.rb b/activerecord/lib/active_record/relational_calculations.rb index d77624c7bf..5e5ea9e0c1 100644 --- a/activerecord/lib/active_record/relational_calculations.rb +++ b/activerecord/lib/active_record/relational_calculations.rb @@ -109,11 +109,11 @@ module ActiveRecord # TODO : relation.projections only works when .select() was last in the chain. Fix it! case args.size when 0 - select = @relation.send(:select_clauses).join(', ') if @relation.respond_to?(:projections) && @relation.projections.present? + select = get_projection_name_from_chained_relations column_name = select if select !~ /(,|\*)/ when 1 if args[0].is_a?(Hash) - select = @relation.send(:select_clauses).join(', ') if @relation.respond_to?(:projections) && @relation.projections.present? + select = get_projection_name_from_chained_relations column_name = select if select !~ /(,|\*)/ options = args[0] else @@ -165,5 +165,20 @@ module ActiveRecord column ? column.type_cast(value) : value end + def get_projection_name_from_chained_relations + name = nil + if @relation.respond_to?(:projections) && @relation.projections.present? + name = @relation.send(:select_clauses).join(', ') + elsif @relation.respond_to?(:relation) && relation = @relation.relation + while relation.respond_to?(:relation) + if relation.respond_to?(:projections) && relation.projections.present? + name = relation.send(:select_clauses).join(', ') + end + relation = relation.relation + end + end + name + end + end end -- cgit v1.2.3 From f17bb1fbff122e94efb3e29f9ce40001c7ee1145 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Wed, 30 Dec 2009 01:16:24 +0530 Subject: Simplify get_projection_name_from_chained_relations using recursion --- .../lib/active_record/relational_calculations.rb | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relational_calculations.rb b/activerecord/lib/active_record/relational_calculations.rb index 5e5ea9e0c1..f42ffe0460 100644 --- a/activerecord/lib/active_record/relational_calculations.rb +++ b/activerecord/lib/active_record/relational_calculations.rb @@ -165,19 +165,12 @@ module ActiveRecord column ? column.type_cast(value) : value end - def get_projection_name_from_chained_relations - name = nil - if @relation.respond_to?(:projections) && @relation.projections.present? - name = @relation.send(:select_clauses).join(', ') - elsif @relation.respond_to?(:relation) && relation = @relation.relation - while relation.respond_to?(:relation) - if relation.respond_to?(:projections) && relation.projections.present? - name = relation.send(:select_clauses).join(', ') - end - relation = relation.relation - end + def get_projection_name_from_chained_relations(relation = @relation) + if relation.respond_to?(:projections) && relation.projections.present? + relation.send(:select_clauses).join(', ') + elsif relation.respond_to?(:relation) + get_projection_name_from_chained_relations(relation.relation) end - name end end -- cgit v1.2.3 From bdf59a561877ab9ac97a89697eaeb8e0db88a408 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Wed, 30 Dec 2009 12:00:26 +0530 Subject: Add Relation#any? and Relation#many? --- activerecord/lib/active_record/relation.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 64261db809..fa517aba38 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -238,6 +238,22 @@ module ActiveRecord loaded? ? @records.empty? : count.zero? end + def any? + if block_given? + to_a.any? { |*block_args| yield(*block_args) } + else + !empty? + end + end + + def many? + if block_given? + to_a.many? { |*block_args| yield(*block_args) } + else + size > 1 + end + end + def destroy_all to_a.each {|object| object.destroy} reset -- cgit v1.2.3 From 8734f9a2710e6ad62caa7e1dcf543b83562e18ed Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Wed, 30 Dec 2009 12:13:55 +0530 Subject: Relation#many? shoud load the records if there's a LIMIT --- activerecord/lib/active_record/relation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index fa517aba38..52d66dc20a 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -250,7 +250,7 @@ module ActiveRecord if block_given? to_a.many? { |*block_args| yield(*block_args) } else - size > 1 + @relation.send(:taken).present? ? to_a.many? : size > 1 end end -- cgit v1.2.3 From 97a64b6b22d87cb6f89a224f7832433b86b545c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 30 Dec 2009 11:43:34 +0100 Subject: Get rid of DeprecatedCallbacks in ActiveRecord::Associations and finally remove it. --- .../lib/active_record/associations/association_collection.rb | 9 ++++++++- activerecord/lib/active_record/callbacks.rb | 2 +- activerecord/lib/active_record/validations.rb | 2 -- 3 files changed, 9 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index b2b3a9789c..1ceb0dbf96 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -485,7 +485,14 @@ module ActiveRecord def callback(method, record) callbacks_for(method).each do |callback| - ActiveSupport::DeprecatedCallbacks::Callback.new(method, callback, record).call(@owner, record) + case callback + when Symbol + @owner.send(callback, record) + when Proc + callback.call(@owner, record) + else + callback.send(method, @owner, record) + end end end diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index e1d772bd95..e2a8f03c8f 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -274,7 +274,7 @@ module ActiveRecord def deprecated_callback_method(symbol) #:nodoc: if respond_to?(symbol) - ActiveSupport::Deprecation.warn("Base##{symbol} has been deprecated, please use Base.#{symbol} :method instead") + ActiveSupport::Deprecation.warn("Overwriting #{symbol} in your models has been deprecated, please use Base##{symbol} :method_name instead") send(symbol) end end diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index e8a2a72735..12c1f23763 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -17,8 +17,6 @@ module ActiveRecord module Validations extend ActiveSupport::Concern - - include ActiveSupport::DeprecatedCallbacks include ActiveModel::Validations included do -- cgit v1.2.3 From 4ecdf24bdedfdd1cca1f079259ff2490e2074067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 30 Dec 2009 13:07:48 +0100 Subject: Kick AR logging back to life and move ControllerRuntime inside ActiveRecord::Rails. --- activerecord/lib/active_record/base.rb | 1 - .../connection_adapters/abstract_adapter.rb | 10 +++++--- .../lib/active_record/controller_runtime.rb | 27 -------------------- activerecord/lib/active_record/rails.rb | 3 ++- .../lib/active_record/rails/controller_runtime.rb | 29 ++++++++++++++++++++++ 5 files changed, 38 insertions(+), 32 deletions(-) delete mode 100644 activerecord/lib/active_record/controller_runtime.rb create mode 100644 activerecord/lib/active_record/rails/controller_runtime.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 07c5545171..c4bdbdad08 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1,4 +1,3 @@ -require 'benchmark' require 'yaml' require 'set' require 'active_support/benchmarkable' diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 8fae26b790..d09aa3c4d2 100755 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -1,6 +1,7 @@ require 'date' require 'bigdecimal' require 'bigdecimal/util' +require 'active_support/core_ext/benchmark' # TODO: Autoload these files require 'active_record/connection_adapters/abstract/schema_definitions' @@ -191,7 +192,6 @@ module ActiveRecord end def log_info(sql, name, ms) - @runtime += ms if @logger && @logger.debug? name = '%s (%.1fms)' % [name || 'SQL', ms] @logger.debug(format_log_entry(name, sql.squeeze(' '))) @@ -199,8 +199,12 @@ module ActiveRecord end protected - def log(sql, name, &block) - ActiveSupport::Notifications.instrument(:sql, :sql => sql, :name => name, &block) + def log(sql, name) + result = nil + ActiveSupport::Notifications.instrument(:sql, :sql => sql, :name => name) do + @runtime += Benchmark.ms { result = yield } + end + result rescue Exception => e # Log message and raise exception. # Set last_verification to 0, so that connection gets verified diff --git a/activerecord/lib/active_record/controller_runtime.rb b/activerecord/lib/active_record/controller_runtime.rb deleted file mode 100644 index 1281901ae8..0000000000 --- a/activerecord/lib/active_record/controller_runtime.rb +++ /dev/null @@ -1,27 +0,0 @@ -module ActiveRecord - module ControllerRuntime - extend ActiveSupport::Concern - - attr_internal :db_runtime - - def cleanup_view_runtime - if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected? - db_rt_before_render = ActiveRecord::Base.connection.reset_runtime - runtime = super - db_rt_after_render = ActiveRecord::Base.connection.reset_runtime - self.db_runtime = db_rt_before_render + db_rt_after_render - runtime - db_rt_after_render - else - super - end - end - - module ClassMethods - def process_log_action(controller) - super - db_runtime = controller.send :db_runtime - logger.info(" ActiveRecord runtime: %.1fms" % db_runtime.to_f) if db_runtime - end - end - end -end \ No newline at end of file diff --git a/activerecord/lib/active_record/rails.rb b/activerecord/lib/active_record/rails.rb index e7de699974..69f38e75c8 100644 --- a/activerecord/lib/active_record/rails.rb +++ b/activerecord/lib/active_record/rails.rb @@ -3,13 +3,14 @@ # here. This is needed for correctly setting up the middleware. # In the future, this might become an optional require. require "action_controller/rails" +require "active_record/rails/controller_runtime" module ActiveRecord class Plugin < Rails::Plugin plugin_name :active_record include_modules_in "ActiveRecord::Base" - config.action_controller.include "ActiveRecord::ControllerRuntime" + config.action_controller.include "ActiveRecord::Rails::ControllerRuntime" rake_tasks do load "active_record/rails/databases.rake" diff --git a/activerecord/lib/active_record/rails/controller_runtime.rb b/activerecord/lib/active_record/rails/controller_runtime.rb new file mode 100644 index 0000000000..9ae40c5c8f --- /dev/null +++ b/activerecord/lib/active_record/rails/controller_runtime.rb @@ -0,0 +1,29 @@ +module ActiveRecord + module Rails + module ControllerRuntime + extend ActiveSupport::Concern + + attr_internal :db_runtime + + def cleanup_view_runtime + if ActiveRecord::Base.connected? + db_rt_before_render = ActiveRecord::Base.connection.reset_runtime + runtime = super + db_rt_after_render = ActiveRecord::Base.connection.reset_runtime + self.db_runtime = db_rt_before_render + db_rt_after_render + runtime - db_rt_after_render + else + super + end + end + + module ClassMethods + def log_process_action(controller) + super + db_runtime = controller.send :db_runtime + logger.info(" ActiveRecord runtime: %.1fms" % db_runtime.to_f) if db_runtime + end + end + end + end +end \ No newline at end of file -- cgit v1.2.3 From 7aabaac0f5ba108f917af2c65a79511694393b85 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Wed, 30 Dec 2009 13:28:26 +0530 Subject: Organize Relation methods into separate modules --- activerecord/lib/active_record.rb | 9 +- activerecord/lib/active_record/relation.rb | 240 +-------------------- .../active_record/relation/calculation_methods.rb | 177 +++++++++++++++ .../lib/active_record/relation/finder_methods.rb | 120 +++++++++++ .../lib/active_record/relation/query_methods.rb | 132 ++++++++++++ .../lib/active_record/relational_calculations.rb | 177 --------------- 6 files changed, 438 insertions(+), 417 deletions(-) create mode 100644 activerecord/lib/active_record/relation/calculation_methods.rb create mode 100644 activerecord/lib/active_record/relation/finder_methods.rb create mode 100644 activerecord/lib/active_record/relation/query_methods.rb delete mode 100644 activerecord/lib/active_record/relational_calculations.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 7031c67539..87de480263 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -47,8 +47,15 @@ module ActiveRecord autoload :AttributeMethods autoload :Attributes autoload :AutosaveAssociation + autoload :Relation - autoload :RelationalCalculations + + autoload_under 'relation' do + autoload :QueryMethods + autoload :FinderMethods + autoload :CalculationMethods + end + autoload :Base autoload :Batches autoload :Calculations diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 52d66dc20a..ae03e1d7e9 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -1,6 +1,6 @@ module ActiveRecord class Relation - include RelationalCalculations + include QueryMethods, FinderMethods, CalculationMethods delegate :to_sql, :to => :relation delegate :length, :collect, :map, :each, :all?, :to => :to_a @@ -32,119 +32,6 @@ module ActiveRecord alias :& :merge - def preload(*associations) - spawn.tap {|r| r.preload_associations += Array.wrap(associations) } - end - - def eager_load(*associations) - spawn.tap {|r| r.eager_load_associations += Array.wrap(associations) } - end - - def readonly(status = true) - spawn.tap {|r| r.readonly = status } - end - - def select(selects) - if selects.present? - relation = spawn(@relation.project(selects)) - relation.readonly = @relation.joins(relation).present? ? false : @readonly - relation - else - spawn - end - end - - def from(from) - from.present? ? spawn(@relation.from(from)) : spawn - end - - def having(*args) - return spawn if args.blank? - - if [String, Hash, Array].include?(args.first.class) - havings = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first) - else - havings = args.first - end - - spawn(@relation.having(havings)) - end - - def group(groups) - groups.present? ? spawn(@relation.group(groups)) : spawn - end - - def order(orders) - orders.present? ? spawn(@relation.order(orders)) : spawn - end - - def lock(locks = true) - case locks - when String - spawn(@relation.lock(locks)) - when TrueClass, NilClass - spawn(@relation.lock) - else - spawn - end - end - - def reverse_order - relation = spawn - relation.instance_variable_set(:@orders, nil) - - order_clause = @relation.send(:order_clauses).join(', ') - if order_clause.present? - relation.order(reverse_sql_order(order_clause)) - else - relation.order("#{@klass.table_name}.#{@klass.primary_key} DESC") - end - end - - def limit(limits) - limits.present? ? spawn(@relation.take(limits)) : spawn - end - - def offset(offsets) - offsets.present? ? spawn(@relation.skip(offsets)) : spawn - end - - def on(join) - spawn(@relation.on(join)) - end - - def joins(join, join_type = nil) - return spawn if join.blank? - - join_relation = case join - when String - @relation.join(join) - when Hash, Array, Symbol - if @klass.send(:array_of_strings?, join) - @relation.join(join.join(' ')) - else - @relation.join(@klass.send(:build_association_joins, join)) - end - else - @relation.join(join, join_type) - end - - spawn(join_relation).tap { |r| r.readonly = true } - end - - def where(*args) - return spawn if args.blank? - - if [String, Hash, Array].include?(args.first.class) - conditions = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first) - conditions = Arel::SqlLiteral.new(conditions) if conditions - else - conditions = args.first - end - - spawn(@relation.where(conditions)) - end - def respond_to?(method, include_private = false) return true if @relation.respond_to?(method, include_private) || Array.method_defined?(method) @@ -189,47 +76,6 @@ module ActiveRecord alias all to_a - def find(*ids, &block) - return to_a.find(&block) if block_given? - - expects_array = ids.first.kind_of?(Array) - return ids.first if expects_array && ids.first.empty? - - ids = ids.flatten.compact.uniq - - case ids.size - when 0 - raise RecordNotFound, "Couldn't find #{@klass.name} without an ID" - when 1 - result = find_one(ids.first) - expects_array ? [ result ] : result - else - find_some(ids) - end - end - - def exists?(id = nil) - relation = select("#{@klass.quoted_table_name}.#{@klass.primary_key}").limit(1) - relation = relation.where(@klass.primary_key => id) if id - relation.first ? true : false - end - - def first - if loaded? - @records.first - else - @first ||= limit(1).to_a[0] - end - end - - def last - if loaded? - @records.last - else - @last ||= reverse_order.limit(1).to_a[0] - end - end - def size loaded? ? @records.length : count end @@ -307,93 +153,9 @@ module ActiveRecord end end - def find_by_attributes(match, attributes, *args) - conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h} - result = where(conditions).send(match.finder) - - if match.bang? && result.blank? - raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}" - else - result - end - end - - def find_or_instantiator_by_attributes(match, attributes, *args) - guard_protected_attributes = false - - if args[0].is_a?(Hash) - guard_protected_attributes = true - attributes_for_create = args[0].with_indifferent_access - conditions = attributes_for_create.slice(*attributes).symbolize_keys - else - attributes_for_create = conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h} - end - - record = where(conditions).first - - unless record - record = @klass.new { |r| r.send(:attributes=, attributes_for_create, guard_protected_attributes) } - yield(record) if block_given? - record.save if match.instantiator == :create - end - - record - end - - def find_one(id) - record = where(@klass.primary_key => id).first - - unless record - conditions = where_clause(', ') - conditions = " [WHERE #{conditions}]" if conditions.present? - raise RecordNotFound, "Couldn't find #{@klass.name} with ID=#{id}#{conditions}" - end - - record - end - - def find_some(ids) - result = where(@klass.primary_key => ids).all - - expected_size = - if @relation.taken && ids.size > @relation.taken - @relation.taken - else - ids.size - end - - # 11 ids with limit 3, offset 9 should give 2 results. - if @relation.skipped && (ids.size - @relation.skipped < expected_size) - expected_size = ids.size - @relation.skipped - end - - if result.size == expected_size - result - else - conditions = where_clause(', ') - conditions = " [WHERE #{conditions}]" if conditions.present? - - error = "Couldn't find all #{@klass.name.pluralize} with IDs " - error << "(#{ids.join(", ")})#{conditions} (found #{result.size} results, but was looking for #{expected_size})" - raise RecordNotFound, error - end - end - def where_clause(join_string = "\n\tAND ") @relation.send(:where_clauses).join(join_string) end - def reverse_sql_order(order_query) - order_query.to_s.split(/,/).each { |s| - if s.match(/\s(asc|ASC)$/) - s.gsub!(/\s(asc|ASC)$/, ' DESC') - elsif s.match(/\s(desc|DESC)$/) - s.gsub!(/\s(desc|DESC)$/, ' ASC') - else - s.concat(' DESC') - end - }.join(',') - end - end end diff --git a/activerecord/lib/active_record/relation/calculation_methods.rb b/activerecord/lib/active_record/relation/calculation_methods.rb new file mode 100644 index 0000000000..a925a99464 --- /dev/null +++ b/activerecord/lib/active_record/relation/calculation_methods.rb @@ -0,0 +1,177 @@ +module ActiveRecord + module CalculationMethods + + def count(*args) + calculate(:count, *construct_count_options_from_args(*args)) + end + + def average(column_name) + calculate(:average, column_name) + end + + def minimum(column_name) + calculate(:minimum, column_name) + end + + def maximum(column_name) + calculate(:maximum, column_name) + end + + def sum(column_name) + calculate(:sum, column_name) + end + + def calculate(operation, column_name, options = {}) + operation = operation.to_s.downcase + + if operation == "count" + joins = @relation.joins(relation) + if joins.present? && joins =~ /LEFT OUTER/i + distinct = true + column_name = @klass.primary_key if column_name == :all + end + + distinct = nil if column_name.to_s =~ /\s*DISTINCT\s+/i + distinct ||= options[:distinct] + else + distinct = nil + end + + distinct = options[:distinct] || distinct + column_name = :all if column_name.blank? && operation == "count" + + if @relation.send(:groupings).any? + return execute_grouped_calculation(operation, column_name) + else + return execute_simple_calculation(operation, column_name, distinct) + end + rescue ThrowResult + 0 + end + + private + + def execute_simple_calculation(operation, column_name, distinct) #:nodoc: + column = if @klass.column_names.include?(column_name.to_s) + Arel::Attribute.new(@klass.arel_table, column_name) + else + Arel::SqlLiteral.new(column_name == :all ? "*" : column_name.to_s) + end + + relation = select(operation == 'count' ? column.count(distinct) : column.send(operation)) + type_cast_calculated_value(@klass.connection.select_value(relation.to_sql), column_for(column_name), operation) + end + + def execute_grouped_calculation(operation, column_name) #:nodoc: + group_attr = @relation.send(:groupings).first.value + association = @klass.reflect_on_association(group_attr.to_sym) + associated = association && association.macro == :belongs_to # only count belongs_to associations + group_field = associated ? association.primary_key_name : group_attr + group_alias = column_alias_for(group_field) + group_column = column_for(group_field) + + group = @klass.connection.adapter_name == 'FrontBase' ? group_alias : group_field + + aggregate_alias = column_alias_for(operation, column_name) + + select_statement = if operation == 'count' && column_name == :all + "COUNT(*) AS count_all" + else + Arel::Attribute.new(@klass.arel_table, column_name).send(operation).as(aggregate_alias).to_sql + end + + select_statement << ", #{group_field} AS #{group_alias}" + + relation = select(select_statement).group(group) + + calculated_data = @klass.connection.select_all(relation.to_sql) + + if association + key_ids = calculated_data.collect { |row| row[group_alias] } + key_records = association.klass.base_class.find(key_ids) + key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) } + end + + calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row| + key = type_cast_calculated_value(row[group_alias], group_column) + key = key_records[key] if associated + value = row[aggregate_alias] + all[key] = type_cast_calculated_value(value, column_for(column_name), operation) + all + end + end + + def construct_count_options_from_args(*args) + options = {} + column_name = :all + + # Handles count(), count(:column), count(:distinct => true), count(:column, :distinct => true) + # TODO : relation.projections only works when .select() was last in the chain. Fix it! + case args.size + when 0 + select = get_projection_name_from_chained_relations + column_name = select if select !~ /(,|\*)/ + when 1 + if args[0].is_a?(Hash) + select = get_projection_name_from_chained_relations + column_name = select if select !~ /(,|\*)/ + options = args[0] + else + column_name = args[0] + end + when 2 + column_name, options = args + else + raise ArgumentError, "Unexpected parameters passed to count(): #{args.inspect}" + end + + [column_name || :all, options] + end + + # Converts the given keys to the value that the database adapter returns as + # a usable column name: + # + # column_alias_for("users.id") # => "users_id" + # column_alias_for("sum(id)") # => "sum_id" + # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id" + # column_alias_for("count(*)") # => "count_all" + # column_alias_for("count", "id") # => "count_id" + def column_alias_for(*keys) + table_name = keys.join(' ') + table_name.downcase! + table_name.gsub!(/\*/, 'all') + table_name.gsub!(/\W+/, ' ') + table_name.strip! + table_name.gsub!(/ +/, '_') + + @klass.connection.table_alias_for(table_name) + end + + def column_for(field) + field_name = field.to_s.split('.').last + @klass.columns.detect { |c| c.name.to_s == field_name } + end + + def type_cast_calculated_value(value, column, operation = nil) + case operation + when 'count' then value.to_i + when 'sum' then type_cast_using_column(value || '0', column) + when 'average' then value && (value.is_a?(Fixnum) ? value.to_f : value).to_d + else type_cast_using_column(value, column) + end + end + + def type_cast_using_column(value, column) + column ? column.type_cast(value) : value + end + + def get_projection_name_from_chained_relations(relation = @relation) + if relation.respond_to?(:projections) && relation.projections.present? + relation.send(:select_clauses).join(', ') + elsif relation.respond_to?(:relation) + get_projection_name_from_chained_relations(relation.relation) + end + end + + end +end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb new file mode 100644 index 0000000000..7a1d6fc538 --- /dev/null +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -0,0 +1,120 @@ +module ActiveRecord + module FinderMethods + + def find(*ids, &block) + return to_a.find(&block) if block_given? + + expects_array = ids.first.kind_of?(Array) + return ids.first if expects_array && ids.first.empty? + + ids = ids.flatten.compact.uniq + + case ids.size + when 0 + raise RecordNotFound, "Couldn't find #{@klass.name} without an ID" + when 1 + result = find_one(ids.first) + expects_array ? [ result ] : result + else + find_some(ids) + end + end + + def exists?(id = nil) + relation = select("#{@klass.quoted_table_name}.#{@klass.primary_key}").limit(1) + relation = relation.where(@klass.primary_key => id) if id + relation.first ? true : false + end + + def first + if loaded? + @records.first + else + @first ||= limit(1).to_a[0] + end + end + + def last + if loaded? + @records.last + else + @last ||= reverse_order.limit(1).to_a[0] + end + end + + protected + + def find_by_attributes(match, attributes, *args) + conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h} + result = where(conditions).send(match.finder) + + if match.bang? && result.blank? + raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}" + else + result + end + end + + def find_or_instantiator_by_attributes(match, attributes, *args) + guard_protected_attributes = false + + if args[0].is_a?(Hash) + guard_protected_attributes = true + attributes_for_create = args[0].with_indifferent_access + conditions = attributes_for_create.slice(*attributes).symbolize_keys + else + attributes_for_create = conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h} + end + + record = where(conditions).first + + unless record + record = @klass.new { |r| r.send(:attributes=, attributes_for_create, guard_protected_attributes) } + yield(record) if block_given? + record.save if match.instantiator == :create + end + + record + end + + def find_one(id) + record = where(@klass.primary_key => id).first + + unless record + conditions = where_clause(', ') + conditions = " [WHERE #{conditions}]" if conditions.present? + raise RecordNotFound, "Couldn't find #{@klass.name} with ID=#{id}#{conditions}" + end + + record + end + + def find_some(ids) + result = where(@klass.primary_key => ids).all + + expected_size = + if @relation.taken && ids.size > @relation.taken + @relation.taken + else + ids.size + end + + # 11 ids with limit 3, offset 9 should give 2 results. + if @relation.skipped && (ids.size - @relation.skipped < expected_size) + expected_size = ids.size - @relation.skipped + end + + if result.size == expected_size + result + else + conditions = where_clause(', ') + conditions = " [WHERE #{conditions}]" if conditions.present? + + error = "Couldn't find all #{@klass.name.pluralize} with IDs " + error << "(#{ids.join(", ")})#{conditions} (found #{result.size} results, but was looking for #{expected_size})" + raise RecordNotFound, error + end + end + + end +end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb new file mode 100644 index 0000000000..631c80da25 --- /dev/null +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -0,0 +1,132 @@ +module ActiveRecord + module QueryMethods + + def preload(*associations) + spawn.tap {|r| r.preload_associations += Array.wrap(associations) } + end + + def eager_load(*associations) + spawn.tap {|r| r.eager_load_associations += Array.wrap(associations) } + end + + def readonly(status = true) + spawn.tap {|r| r.readonly = status } + end + + def select(selects) + if selects.present? + relation = spawn(@relation.project(selects)) + relation.readonly = @relation.joins(relation).present? ? false : @readonly + relation + else + spawn + end + end + + def from(from) + from.present? ? spawn(@relation.from(from)) : spawn + end + + def having(*args) + return spawn if args.blank? + + if [String, Hash, Array].include?(args.first.class) + havings = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first) + else + havings = args.first + end + + spawn(@relation.having(havings)) + end + + def group(groups) + groups.present? ? spawn(@relation.group(groups)) : spawn + end + + def order(orders) + orders.present? ? spawn(@relation.order(orders)) : spawn + end + + def lock(locks = true) + case locks + when String + spawn(@relation.lock(locks)) + when TrueClass, NilClass + spawn(@relation.lock) + else + spawn + end + end + + def reverse_order + relation = spawn + relation.instance_variable_set(:@orders, nil) + + order_clause = @relation.send(:order_clauses).join(', ') + if order_clause.present? + relation.order(reverse_sql_order(order_clause)) + else + relation.order("#{@klass.table_name}.#{@klass.primary_key} DESC") + end + end + + def limit(limits) + limits.present? ? spawn(@relation.take(limits)) : spawn + end + + def offset(offsets) + offsets.present? ? spawn(@relation.skip(offsets)) : spawn + end + + def on(join) + spawn(@relation.on(join)) + end + + def joins(join, join_type = nil) + return spawn if join.blank? + + join_relation = case join + when String + @relation.join(join) + when Hash, Array, Symbol + if @klass.send(:array_of_strings?, join) + @relation.join(join.join(' ')) + else + @relation.join(@klass.send(:build_association_joins, join)) + end + else + @relation.join(join, join_type) + end + + spawn(join_relation).tap { |r| r.readonly = true } + end + + def where(*args) + return spawn if args.blank? + + if [String, Hash, Array].include?(args.first.class) + conditions = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first) + conditions = Arel::SqlLiteral.new(conditions) if conditions + else + conditions = args.first + end + + spawn(@relation.where(conditions)) + end + + private + + def reverse_sql_order(order_query) + order_query.to_s.split(/,/).each { |s| + if s.match(/\s(asc|ASC)$/) + s.gsub!(/\s(asc|ASC)$/, ' DESC') + elsif s.match(/\s(desc|DESC)$/) + s.gsub!(/\s(desc|DESC)$/, ' ASC') + else + s.concat(' DESC') + end + }.join(',') + end + + end +end diff --git a/activerecord/lib/active_record/relational_calculations.rb b/activerecord/lib/active_record/relational_calculations.rb deleted file mode 100644 index f42ffe0460..0000000000 --- a/activerecord/lib/active_record/relational_calculations.rb +++ /dev/null @@ -1,177 +0,0 @@ -module ActiveRecord - module RelationalCalculations - - def count(*args) - calculate(:count, *construct_count_options_from_args(*args)) - end - - def average(column_name) - calculate(:average, column_name) - end - - def minimum(column_name) - calculate(:minimum, column_name) - end - - def maximum(column_name) - calculate(:maximum, column_name) - end - - def sum(column_name) - calculate(:sum, column_name) - end - - def calculate(operation, column_name, options = {}) - operation = operation.to_s.downcase - - if operation == "count" - joins = @relation.joins(relation) - if joins.present? && joins =~ /LEFT OUTER/i - distinct = true - column_name = @klass.primary_key if column_name == :all - end - - distinct = nil if column_name.to_s =~ /\s*DISTINCT\s+/i - distinct ||= options[:distinct] - else - distinct = nil - end - - distinct = options[:distinct] || distinct - column_name = :all if column_name.blank? && operation == "count" - - if @relation.send(:groupings).any? - return execute_grouped_calculation(operation, column_name) - else - return execute_simple_calculation(operation, column_name, distinct) - end - rescue ThrowResult - 0 - end - - private - - def execute_simple_calculation(operation, column_name, distinct) #:nodoc: - column = if @klass.column_names.include?(column_name.to_s) - Arel::Attribute.new(@klass.arel_table, column_name) - else - Arel::SqlLiteral.new(column_name == :all ? "*" : column_name.to_s) - end - - relation = select(operation == 'count' ? column.count(distinct) : column.send(operation)) - type_cast_calculated_value(@klass.connection.select_value(relation.to_sql), column_for(column_name), operation) - end - - def execute_grouped_calculation(operation, column_name) #:nodoc: - group_attr = @relation.send(:groupings).first.value - association = @klass.reflect_on_association(group_attr.to_sym) - associated = association && association.macro == :belongs_to # only count belongs_to associations - group_field = associated ? association.primary_key_name : group_attr - group_alias = column_alias_for(group_field) - group_column = column_for(group_field) - - group = @klass.connection.adapter_name == 'FrontBase' ? group_alias : group_field - - aggregate_alias = column_alias_for(operation, column_name) - - select_statement = if operation == 'count' && column_name == :all - "COUNT(*) AS count_all" - else - Arel::Attribute.new(@klass.arel_table, column_name).send(operation).as(aggregate_alias).to_sql - end - - select_statement << ", #{group_field} AS #{group_alias}" - - relation = select(select_statement).group(group) - - calculated_data = @klass.connection.select_all(relation.to_sql) - - if association - key_ids = calculated_data.collect { |row| row[group_alias] } - key_records = association.klass.base_class.find(key_ids) - key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) } - end - - calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row| - key = type_cast_calculated_value(row[group_alias], group_column) - key = key_records[key] if associated - value = row[aggregate_alias] - all[key] = type_cast_calculated_value(value, column_for(column_name), operation) - all - end - end - - def construct_count_options_from_args(*args) - options = {} - column_name = :all - - # Handles count(), count(:column), count(:distinct => true), count(:column, :distinct => true) - # TODO : relation.projections only works when .select() was last in the chain. Fix it! - case args.size - when 0 - select = get_projection_name_from_chained_relations - column_name = select if select !~ /(,|\*)/ - when 1 - if args[0].is_a?(Hash) - select = get_projection_name_from_chained_relations - column_name = select if select !~ /(,|\*)/ - options = args[0] - else - column_name = args[0] - end - when 2 - column_name, options = args - else - raise ArgumentError, "Unexpected parameters passed to count(): #{args.inspect}" - end - - [column_name || :all, options] - end - - # Converts the given keys to the value that the database adapter returns as - # a usable column name: - # - # column_alias_for("users.id") # => "users_id" - # column_alias_for("sum(id)") # => "sum_id" - # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id" - # column_alias_for("count(*)") # => "count_all" - # column_alias_for("count", "id") # => "count_id" - def column_alias_for(*keys) - table_name = keys.join(' ') - table_name.downcase! - table_name.gsub!(/\*/, 'all') - table_name.gsub!(/\W+/, ' ') - table_name.strip! - table_name.gsub!(/ +/, '_') - - @klass.connection.table_alias_for(table_name) - end - - def column_for(field) - field_name = field.to_s.split('.').last - @klass.columns.detect { |c| c.name.to_s == field_name } - end - - def type_cast_calculated_value(value, column, operation = nil) - case operation - when 'count' then value.to_i - when 'sum' then type_cast_using_column(value || '0', column) - when 'average' then value && (value.is_a?(Fixnum) ? value.to_f : value).to_d - else type_cast_using_column(value, column) - end - end - - def type_cast_using_column(value, column) - column ? column.type_cast(value) : value - end - - def get_projection_name_from_chained_relations(relation = @relation) - if relation.respond_to?(:projections) && relation.projections.present? - relation.send(:select_clauses).join(', ') - elsif relation.respond_to?(:relation) - get_projection_name_from_chained_relations(relation.relation) - end - end - - end -end -- cgit v1.2.3 From 1a0c372b19b6d6521df7c39a01a02ef5350ab300 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Wed, 30 Dec 2009 14:31:02 -0800 Subject: depends on Module#attr_internal --- activerecord/lib/active_record/rails/controller_runtime.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/rails/controller_runtime.rb b/activerecord/lib/active_record/rails/controller_runtime.rb index 9ae40c5c8f..b8d5a77a0d 100644 --- a/activerecord/lib/active_record/rails/controller_runtime.rb +++ b/activerecord/lib/active_record/rails/controller_runtime.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/module/attr_internal' + module ActiveRecord module Rails module ControllerRuntime @@ -26,4 +28,4 @@ module ActiveRecord end end end -end \ No newline at end of file +end -- cgit v1.2.3 From 10a2638db099baddfa46da98f780df3a72a718d4 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Wed, 30 Dec 2009 19:24:00 -0800 Subject: Fix up AR extension by using Railties module instead of Rails and requiring ControllerRuntime at initialization time instead of boot --- activerecord/lib/active_record.rb | 1 - activerecord/lib/active_record/rails.rb | 15 +- .../lib/active_record/rails/controller_runtime.rb | 31 -- .../lib/active_record/rails/databases.rake | 469 --------------------- .../active_record/railties/controller_runtime.rb | 31 ++ .../lib/active_record/railties/databases.rake | 469 +++++++++++++++++++++ 6 files changed, 509 insertions(+), 507 deletions(-) delete mode 100644 activerecord/lib/active_record/rails/controller_runtime.rb delete mode 100644 activerecord/lib/active_record/rails/databases.rake create mode 100644 activerecord/lib/active_record/railties/controller_runtime.rb create mode 100644 activerecord/lib/active_record/railties/databases.rake (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 87de480263..a524dc50a1 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -60,7 +60,6 @@ module ActiveRecord autoload :Batches autoload :Calculations autoload :Callbacks - autoload :ControllerRuntime autoload :DynamicFinderMatch autoload :DynamicScopeMatch autoload :Migration diff --git a/activerecord/lib/active_record/rails.rb b/activerecord/lib/active_record/rails.rb index 69f38e75c8..8bc49c3d96 100644 --- a/activerecord/lib/active_record/rails.rb +++ b/activerecord/lib/active_record/rails.rb @@ -3,17 +3,14 @@ # here. This is needed for correctly setting up the middleware. # In the future, this might become an optional require. require "action_controller/rails" -require "active_record/rails/controller_runtime" module ActiveRecord class Plugin < Rails::Plugin plugin_name :active_record include_modules_in "ActiveRecord::Base" - config.action_controller.include "ActiveRecord::Rails::ControllerRuntime" - rake_tasks do - load "active_record/rails/databases.rake" + load "active_record/railties/databases.rake" end initializer "active_record.set_configs" do |app| @@ -34,6 +31,12 @@ module ActiveRecord ActiveRecord::Base.default_timezone = :utc end + # Expose database runtime to controller for logging. + initializer "active_record.log_runtime" do |app| + require "active_record/railties/controller_runtime" + ActionController::Base.send :include, ActiveRecord::Railties::ControllerRuntime + end + # Setup database middleware after initializers have run initializer "active_record.initialize_database_middleware" do |app| middleware = app.config.middleware @@ -52,7 +55,7 @@ module ActiveRecord # TODO: ActiveRecord::Base.logger should delegate to its own config.logger initializer "active_record.logger" do - ActiveRecord::Base.logger ||= Rails.logger + ActiveRecord::Base.logger ||= ::Rails.logger end initializer "active_record.notifications" do @@ -64,4 +67,4 @@ module ActiveRecord end end -end \ No newline at end of file +end diff --git a/activerecord/lib/active_record/rails/controller_runtime.rb b/activerecord/lib/active_record/rails/controller_runtime.rb deleted file mode 100644 index b8d5a77a0d..0000000000 --- a/activerecord/lib/active_record/rails/controller_runtime.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'active_support/core_ext/module/attr_internal' - -module ActiveRecord - module Rails - module ControllerRuntime - extend ActiveSupport::Concern - - attr_internal :db_runtime - - def cleanup_view_runtime - if ActiveRecord::Base.connected? - db_rt_before_render = ActiveRecord::Base.connection.reset_runtime - runtime = super - db_rt_after_render = ActiveRecord::Base.connection.reset_runtime - self.db_runtime = db_rt_before_render + db_rt_after_render - runtime - db_rt_after_render - else - super - end - end - - module ClassMethods - def log_process_action(controller) - super - db_runtime = controller.send :db_runtime - logger.info(" ActiveRecord runtime: %.1fms" % db_runtime.to_f) if db_runtime - end - end - end - end -end diff --git a/activerecord/lib/active_record/rails/databases.rake b/activerecord/lib/active_record/rails/databases.rake deleted file mode 100644 index a35a6c156b..0000000000 --- a/activerecord/lib/active_record/rails/databases.rake +++ /dev/null @@ -1,469 +0,0 @@ -namespace :db do - task :load_config => :rails_env do - require 'active_record' - ActiveRecord::Base.configurations = Rails::Configuration.new.database_configuration - end - - namespace :create do - desc 'Create all the local databases defined in config/database.yml' - task :all => :load_config do - ActiveRecord::Base.configurations.each_value do |config| - # Skip entries that don't have a database key, such as the first entry here: - # - # defaults: &defaults - # adapter: mysql - # username: root - # password: - # host: localhost - # - # development: - # database: blog_development - # <<: *defaults - next unless config['database'] - # Only connect to local databases - local_database?(config) { create_database(config) } - end - end - end - - desc 'Create the database defined in config/database.yml for the current RAILS_ENV' - task :create => :load_config do - create_database(ActiveRecord::Base.configurations[RAILS_ENV]) - end - - def create_database(config) - begin - if config['adapter'] =~ /sqlite/ - if File.exist?(config['database']) - $stderr.puts "#{config['database']} already exists" - else - begin - # Create the SQLite database - ActiveRecord::Base.establish_connection(config) - ActiveRecord::Base.connection - rescue - $stderr.puts $!, *($!.backtrace) - $stderr.puts "Couldn't create database for #{config.inspect}" - end - end - return # Skip the else clause of begin/rescue - else - ActiveRecord::Base.establish_connection(config) - ActiveRecord::Base.connection - end - rescue - case config['adapter'] - when 'mysql' - @charset = ENV['CHARSET'] || 'utf8' - @collation = ENV['COLLATION'] || 'utf8_unicode_ci' - creation_options = {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)} - begin - ActiveRecord::Base.establish_connection(config.merge('database' => nil)) - ActiveRecord::Base.connection.create_database(config['database'], creation_options) - ActiveRecord::Base.establish_connection(config) - rescue Mysql::Error => sqlerr - if sqlerr.errno == Mysql::Error::ER_ACCESS_DENIED_ERROR - print "#{sqlerr.error}. \nPlease provide the root password for your mysql installation\n>" - root_password = $stdin.gets.strip - grant_statement = "GRANT ALL PRIVILEGES ON #{config['database']}.* " \ - "TO '#{config['username']}'@'localhost' " \ - "IDENTIFIED BY '#{config['password']}' WITH GRANT OPTION;" - ActiveRecord::Base.establish_connection(config.merge( - 'database' => nil, 'username' => 'root', 'password' => root_password)) - ActiveRecord::Base.connection.create_database(config['database'], creation_options) - ActiveRecord::Base.connection.execute grant_statement - ActiveRecord::Base.establish_connection(config) - else - $stderr.puts sqlerr.error - $stderr.puts "Couldn't create database for #{config.inspect}, charset: #{config['charset'] || @charset}, collation: #{config['collation'] || @collation}" - $stderr.puts "(if you set the charset manually, make sure you have a matching collation)" if config['charset'] - end - end - when 'postgresql' - @encoding = config[:encoding] || ENV['CHARSET'] || 'utf8' - begin - ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public')) - ActiveRecord::Base.connection.create_database(config['database'], config.merge('encoding' => @encoding)) - ActiveRecord::Base.establish_connection(config) - rescue - $stderr.puts $!, *($!.backtrace) - $stderr.puts "Couldn't create database for #{config.inspect}" - end - end - else - $stderr.puts "#{config['database']} already exists" - end - end - - namespace :drop do - desc 'Drops all the local databases defined in config/database.yml' - task :all => :load_config do - ActiveRecord::Base.configurations.each_value do |config| - # Skip entries that don't have a database key - next unless config['database'] - begin - # Only connect to local databases - local_database?(config) { drop_database(config) } - rescue Exception => e - puts "Couldn't drop #{config['database']} : #{e.inspect}" - end - end - end - end - - desc 'Drops the database for the current RAILS_ENV' - task :drop => :load_config do - config = ActiveRecord::Base.configurations[RAILS_ENV || 'development'] - begin - drop_database(config) - rescue Exception => e - puts "Couldn't drop #{config['database']} : #{e.inspect}" - end - end - - def local_database?(config, &block) - if %w( 127.0.0.1 localhost ).include?(config['host']) || config['host'].blank? - yield - else - puts "This task only modifies local databases. #{config['database']} is on a remote host." - end - end - - - desc "Migrate the database through scripts in db/migrate and update db/schema.rb by invoking db:schema:dump. Target specific version with VERSION=x. Turn off output with VERBOSE=false." - task :migrate => :environment do - ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true - ActiveRecord::Migrator.migrate("db/migrate/", ENV["VERSION"] ? ENV["VERSION"].to_i : nil) - Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby - end - - namespace :migrate do - desc 'Rollbacks the database one migration and re migrate up. If you want to rollback more than one step, define STEP=x. Target specific version with VERSION=x.' - task :redo => :environment do - if ENV["VERSION"] - Rake::Task["db:migrate:down"].invoke - Rake::Task["db:migrate:up"].invoke - else - Rake::Task["db:rollback"].invoke - Rake::Task["db:migrate"].invoke - end - end - - desc 'Resets your database using your migrations for the current environment' - task :reset => ["db:drop", "db:create", "db:migrate"] - - desc 'Runs the "up" for a given migration VERSION.' - task :up => :environment do - version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil - raise "VERSION is required" unless version - ActiveRecord::Migrator.run(:up, "db/migrate/", version) - Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby - end - - desc 'Runs the "down" for a given migration VERSION.' - task :down => :environment do - version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil - raise "VERSION is required" unless version - ActiveRecord::Migrator.run(:down, "db/migrate/", version) - Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby - end - end - - desc 'Rolls the schema back to the previous version. Specify the number of steps with STEP=n' - task :rollback => :environment do - step = ENV['STEP'] ? ENV['STEP'].to_i : 1 - ActiveRecord::Migrator.rollback('db/migrate/', step) - Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby - end - - desc 'Pushes the schema to the next version. Specify the number of steps with STEP=n' - task :forward => :environment do - step = ENV['STEP'] ? ENV['STEP'].to_i : 1 - ActiveRecord::Migrator.forward('db/migrate/', step) - Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby - end - - desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.' - task :reset => [ 'db:drop', 'db:setup' ] - - desc "Retrieves the charset for the current environment's database" - task :charset => :environment do - config = ActiveRecord::Base.configurations[RAILS_ENV || 'development'] - case config['adapter'] - when 'mysql' - ActiveRecord::Base.establish_connection(config) - puts ActiveRecord::Base.connection.charset - when 'postgresql' - ActiveRecord::Base.establish_connection(config) - puts ActiveRecord::Base.connection.encoding - else - puts 'sorry, your database adapter is not supported yet, feel free to submit a patch' - end - end - - desc "Retrieves the collation for the current environment's database" - task :collation => :environment do - config = ActiveRecord::Base.configurations[RAILS_ENV || 'development'] - case config['adapter'] - when 'mysql' - ActiveRecord::Base.establish_connection(config) - puts ActiveRecord::Base.connection.collation - else - puts 'sorry, your database adapter is not supported yet, feel free to submit a patch' - end - end - - desc "Retrieves the current schema version number" - task :version => :environment do - puts "Current version: #{ActiveRecord::Migrator.current_version}" - end - - desc "Raises an error if there are pending migrations" - task :abort_if_pending_migrations => :environment do - if defined? ActiveRecord - pending_migrations = ActiveRecord::Migrator.new(:up, 'db/migrate').pending_migrations - - if pending_migrations.any? - puts "You have #{pending_migrations.size} pending migrations:" - pending_migrations.each do |pending_migration| - puts ' %4d %s' % [pending_migration.version, pending_migration.name] - end - abort %{Run "rake db:migrate" to update your database then try again.} - end - end - end - - desc 'Create the database, load the schema, and initialize with the seed data' - task :setup => [ 'db:create', 'db:schema:load', 'db:seed' ] - - desc 'Load the seed data from db/seeds.rb' - task :seed => :environment do - seed_file = File.join(Rails.root, 'db', 'seeds.rb') - load(seed_file) if File.exist?(seed_file) - end - - namespace :fixtures do - desc "Load fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures." - task :load => :environment do - require 'active_record/fixtures' - ActiveRecord::Base.establish_connection(Rails.env) - base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures') - fixtures_dir = ENV['FIXTURES_DIR'] ? File.join(base_dir, ENV['FIXTURES_DIR']) : base_dir - - (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/).map {|f| File.join(fixtures_dir, f) } : Dir.glob(File.join(fixtures_dir, '*.{yml,csv}'))).each do |fixture_file| - Fixtures.create_fixtures(File.dirname(fixture_file), File.basename(fixture_file, '.*')) - end - end - - desc "Search for a fixture given a LABEL or ID. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures." - task :identify => :environment do - require "active_record/fixtures" - - label, id = ENV["LABEL"], ENV["ID"] - raise "LABEL or ID required" if label.blank? && id.blank? - - puts %Q(The fixture ID for "#{label}" is #{Fixtures.identify(label)}.) if label - - base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures') - Dir["#{base_dir}/**/*.yml"].each do |file| - if data = YAML::load(ERB.new(IO.read(file)).result) - data.keys.each do |key| - key_id = Fixtures.identify(key) - - if key == label || key_id == id.to_i - puts "#{file}: #{key} (#{key_id})" - end - end - end - end - end - end - - namespace :schema do - desc "Create a db/schema.rb file that can be portably used against any DB supported by AR" - task :dump => :environment do - require 'active_record/schema_dumper' - File.open(ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb", "w") do |file| - ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file) - end - Rake::Task["db:schema:dump"].reenable - end - - desc "Load a schema.rb file into the database" - task :load => :environment do - file = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb" - if File.exists?(file) - load(file) - else - abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to prevent active_record from loading: config.frameworks -= [ :active_record ]} - end - end - end - - namespace :structure do - desc "Dump the database structure to a SQL file" - task :dump => :environment do - abcs = ActiveRecord::Base.configurations - case abcs[RAILS_ENV]["adapter"] - 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" - ENV['PGHOST'] = abcs[RAILS_ENV]["host"] if abcs[RAILS_ENV]["host"] - ENV['PGPORT'] = abcs[RAILS_ENV]["port"].to_s if abcs[RAILS_ENV]["port"] - ENV['PGPASSWORD'] = abcs[RAILS_ENV]["password"].to_s if abcs[RAILS_ENV]["password"] - search_path = abcs[RAILS_ENV]["schema_search_path"] - unless search_path.blank? - search_path = search_path.split(",").map{|search_path| "--schema=#{search_path.strip}" }.join(" ") - end - `pg_dump -i -U "#{abcs[RAILS_ENV]["username"]}" -s -x -O -f db/#{RAILS_ENV}_structure.sql #{search_path} #{abcs[RAILS_ENV]["database"]}` - raise "Error dumping database" if $?.exitstatus == 1 - when "sqlite", "sqlite3" - dbfile = abcs[RAILS_ENV]["database"] || abcs[RAILS_ENV]["dbfile"] - `#{abcs[RAILS_ENV]["adapter"]} #{dbfile} .schema > db/#{RAILS_ENV}_structure.sql` - when "sqlserver" - `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /f db\\#{RAILS_ENV}_structure.sql /q /A /r` - `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /F db\ /q /A /r` - when "firebird" - set_firebird_env(abcs[RAILS_ENV]) - db_string = firebird_db_string(abcs[RAILS_ENV]) - sh "isql -a #{db_string} > #{Rails.root}/db/#{RAILS_ENV}_structure.sql" - else - raise "Task not supported by '#{abcs["test"]["adapter"]}'" - end - - if ActiveRecord::Base.connection.supports_migrations? - File.open("#{Rails.root}/db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information } - end - end - end - - namespace :test do - desc "Recreate the test database from the current schema.rb" - task :load => 'db:test:purge' do - ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test']) - ActiveRecord::Schema.verbose = false - Rake::Task["db:schema:load"].invoke - end - - desc "Recreate the test database from the current environment's database schema" - task :clone => %w(db:schema:dump db:test:load) - - desc "Recreate the test databases from the development structure" - task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do - abcs = ActiveRecord::Base.configurations - case abcs["test"]["adapter"] - 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| - ActiveRecord::Base.connection.execute(table) - end - when "postgresql" - ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"] - ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"] - ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"] - `psql -U "#{abcs["test"]["username"]}" -f #{Rails.root}/db/#{RAILS_ENV}_structure.sql #{abcs["test"]["database"]}` - when "sqlite", "sqlite3" - dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"] - `#{abcs["test"]["adapter"]} #{dbfile} < #{Rails.root}/db/#{RAILS_ENV}_structure.sql` - when "sqlserver" - `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql` - when "oci", "oracle" - ActiveRecord::Base.establish_connection(:test) - IO.readlines("#{Rails.root}/db/#{RAILS_ENV}_structure.sql").join.split(";\n\n").each do |ddl| - ActiveRecord::Base.connection.execute(ddl) - end - when "firebird" - set_firebird_env(abcs["test"]) - db_string = firebird_db_string(abcs["test"]) - sh "isql -i #{Rails.root}/db/#{RAILS_ENV}_structure.sql #{db_string}" - else - raise "Task not supported by '#{abcs["test"]["adapter"]}'" - end - end - - desc "Empty the test database" - task :purge => :environment do - abcs = ActiveRecord::Base.configurations - case abcs["test"]["adapter"] - when "mysql" - ActiveRecord::Base.establish_connection(:test) - ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"], abcs["test"]) - when "postgresql" - ActiveRecord::Base.clear_active_connections! - drop_database(abcs['test']) - create_database(abcs['test']) - when "sqlite","sqlite3" - dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"] - File.delete(dbfile) if File.exist?(dbfile) - when "sqlserver" - dropfkscript = "#{abcs["test"]["host"]}.#{abcs["test"]["database"]}.DP1".gsub(/\\/,'-') - `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{dropfkscript}` - `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql` - when "oci", "oracle" - ActiveRecord::Base.establish_connection(:test) - ActiveRecord::Base.connection.structure_drop.split(";\n\n").each do |ddl| - ActiveRecord::Base.connection.execute(ddl) - end - when "firebird" - ActiveRecord::Base.establish_connection(:test) - ActiveRecord::Base.connection.recreate_database! - else - raise "Task not supported by '#{abcs["test"]["adapter"]}'" - end - end - - desc 'Check for pending migrations and load the test schema' - task :prepare => 'db:abort_if_pending_migrations' do - if defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank? - Rake::Task[{ :sql => "db:test:clone_structure", :ruby => "db:test:load" }[ActiveRecord::Base.schema_format]].invoke - end - end - end - - namespace :sessions do - desc "Creates a sessions migration for use with ActiveRecord::SessionStore" - task :create => :environment do - raise "Task unavailable to this database (no migration support)" unless ActiveRecord::Base.connection.supports_migrations? - require 'rails/generators' - require 'rails/generators/rails/session_migration/session_migration_generator' - Rails::Generators::SessionMigrationGenerator.start [ ENV["MIGRATION"] || "add_sessions_table" ] - end - - desc "Clear the sessions table" - task :clear => :environment do - ActiveRecord::Base.connection.execute "DELETE FROM #{session_table_name}" - end - end -end - -def drop_database(config) - case config['adapter'] - when 'mysql' - ActiveRecord::Base.establish_connection(config) - ActiveRecord::Base.connection.drop_database config['database'] - when /^sqlite/ - require 'pathname' - path = Pathname.new(config['database']) - file = path.absolute? ? path.to_s : File.join(Rails.root, path) - - FileUtils.rm(file) - when 'postgresql' - ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public')) - ActiveRecord::Base.connection.drop_database config['database'] - end -end - -def session_table_name - ActiveRecord::SessionStore::Session.table_name -end - -def set_firebird_env(config) - ENV["ISC_USER"] = config["username"].to_s if config["username"] - ENV["ISC_PASSWORD"] = config["password"].to_s if config["password"] -end - -def firebird_db_string(config) - FireRuby::Database.db_string_for(config.symbolize_keys) -end diff --git a/activerecord/lib/active_record/railties/controller_runtime.rb b/activerecord/lib/active_record/railties/controller_runtime.rb new file mode 100644 index 0000000000..535e967ec3 --- /dev/null +++ b/activerecord/lib/active_record/railties/controller_runtime.rb @@ -0,0 +1,31 @@ +require 'active_support/core_ext/module/attr_internal' + +module ActiveRecord + module Railties + module ControllerRuntime + extend ActiveSupport::Concern + + attr_internal :db_runtime + + def cleanup_view_runtime + if ActiveRecord::Base.connected? + db_rt_before_render = ActiveRecord::Base.connection.reset_runtime + runtime = super + db_rt_after_render = ActiveRecord::Base.connection.reset_runtime + self.db_runtime = db_rt_before_render + db_rt_after_render + runtime - db_rt_after_render + else + super + end + end + + module ClassMethods + def log_process_action(controller) + super + db_runtime = controller.send :db_runtime + logger.info(" ActiveRecord runtime: %.1fms" % db_runtime.to_f) if db_runtime + end + end + end + end +end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake new file mode 100644 index 0000000000..a35a6c156b --- /dev/null +++ b/activerecord/lib/active_record/railties/databases.rake @@ -0,0 +1,469 @@ +namespace :db do + task :load_config => :rails_env do + require 'active_record' + ActiveRecord::Base.configurations = Rails::Configuration.new.database_configuration + end + + namespace :create do + desc 'Create all the local databases defined in config/database.yml' + task :all => :load_config do + ActiveRecord::Base.configurations.each_value do |config| + # Skip entries that don't have a database key, such as the first entry here: + # + # defaults: &defaults + # adapter: mysql + # username: root + # password: + # host: localhost + # + # development: + # database: blog_development + # <<: *defaults + next unless config['database'] + # Only connect to local databases + local_database?(config) { create_database(config) } + end + end + end + + desc 'Create the database defined in config/database.yml for the current RAILS_ENV' + task :create => :load_config do + create_database(ActiveRecord::Base.configurations[RAILS_ENV]) + end + + def create_database(config) + begin + if config['adapter'] =~ /sqlite/ + if File.exist?(config['database']) + $stderr.puts "#{config['database']} already exists" + else + begin + # Create the SQLite database + ActiveRecord::Base.establish_connection(config) + ActiveRecord::Base.connection + rescue + $stderr.puts $!, *($!.backtrace) + $stderr.puts "Couldn't create database for #{config.inspect}" + end + end + return # Skip the else clause of begin/rescue + else + ActiveRecord::Base.establish_connection(config) + ActiveRecord::Base.connection + end + rescue + case config['adapter'] + when 'mysql' + @charset = ENV['CHARSET'] || 'utf8' + @collation = ENV['COLLATION'] || 'utf8_unicode_ci' + creation_options = {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)} + begin + ActiveRecord::Base.establish_connection(config.merge('database' => nil)) + ActiveRecord::Base.connection.create_database(config['database'], creation_options) + ActiveRecord::Base.establish_connection(config) + rescue Mysql::Error => sqlerr + if sqlerr.errno == Mysql::Error::ER_ACCESS_DENIED_ERROR + print "#{sqlerr.error}. \nPlease provide the root password for your mysql installation\n>" + root_password = $stdin.gets.strip + grant_statement = "GRANT ALL PRIVILEGES ON #{config['database']}.* " \ + "TO '#{config['username']}'@'localhost' " \ + "IDENTIFIED BY '#{config['password']}' WITH GRANT OPTION;" + ActiveRecord::Base.establish_connection(config.merge( + 'database' => nil, 'username' => 'root', 'password' => root_password)) + ActiveRecord::Base.connection.create_database(config['database'], creation_options) + ActiveRecord::Base.connection.execute grant_statement + ActiveRecord::Base.establish_connection(config) + else + $stderr.puts sqlerr.error + $stderr.puts "Couldn't create database for #{config.inspect}, charset: #{config['charset'] || @charset}, collation: #{config['collation'] || @collation}" + $stderr.puts "(if you set the charset manually, make sure you have a matching collation)" if config['charset'] + end + end + when 'postgresql' + @encoding = config[:encoding] || ENV['CHARSET'] || 'utf8' + begin + ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public')) + ActiveRecord::Base.connection.create_database(config['database'], config.merge('encoding' => @encoding)) + ActiveRecord::Base.establish_connection(config) + rescue + $stderr.puts $!, *($!.backtrace) + $stderr.puts "Couldn't create database for #{config.inspect}" + end + end + else + $stderr.puts "#{config['database']} already exists" + end + end + + namespace :drop do + desc 'Drops all the local databases defined in config/database.yml' + task :all => :load_config do + ActiveRecord::Base.configurations.each_value do |config| + # Skip entries that don't have a database key + next unless config['database'] + begin + # Only connect to local databases + local_database?(config) { drop_database(config) } + rescue Exception => e + puts "Couldn't drop #{config['database']} : #{e.inspect}" + end + end + end + end + + desc 'Drops the database for the current RAILS_ENV' + task :drop => :load_config do + config = ActiveRecord::Base.configurations[RAILS_ENV || 'development'] + begin + drop_database(config) + rescue Exception => e + puts "Couldn't drop #{config['database']} : #{e.inspect}" + end + end + + def local_database?(config, &block) + if %w( 127.0.0.1 localhost ).include?(config['host']) || config['host'].blank? + yield + else + puts "This task only modifies local databases. #{config['database']} is on a remote host." + end + end + + + desc "Migrate the database through scripts in db/migrate and update db/schema.rb by invoking db:schema:dump. Target specific version with VERSION=x. Turn off output with VERBOSE=false." + task :migrate => :environment do + ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true + ActiveRecord::Migrator.migrate("db/migrate/", ENV["VERSION"] ? ENV["VERSION"].to_i : nil) + Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby + end + + namespace :migrate do + desc 'Rollbacks the database one migration and re migrate up. If you want to rollback more than one step, define STEP=x. Target specific version with VERSION=x.' + task :redo => :environment do + if ENV["VERSION"] + Rake::Task["db:migrate:down"].invoke + Rake::Task["db:migrate:up"].invoke + else + Rake::Task["db:rollback"].invoke + Rake::Task["db:migrate"].invoke + end + end + + desc 'Resets your database using your migrations for the current environment' + task :reset => ["db:drop", "db:create", "db:migrate"] + + desc 'Runs the "up" for a given migration VERSION.' + task :up => :environment do + version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil + raise "VERSION is required" unless version + ActiveRecord::Migrator.run(:up, "db/migrate/", version) + Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby + end + + desc 'Runs the "down" for a given migration VERSION.' + task :down => :environment do + version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil + raise "VERSION is required" unless version + ActiveRecord::Migrator.run(:down, "db/migrate/", version) + Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby + end + end + + desc 'Rolls the schema back to the previous version. Specify the number of steps with STEP=n' + task :rollback => :environment do + step = ENV['STEP'] ? ENV['STEP'].to_i : 1 + ActiveRecord::Migrator.rollback('db/migrate/', step) + Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby + end + + desc 'Pushes the schema to the next version. Specify the number of steps with STEP=n' + task :forward => :environment do + step = ENV['STEP'] ? ENV['STEP'].to_i : 1 + ActiveRecord::Migrator.forward('db/migrate/', step) + Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby + end + + desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.' + task :reset => [ 'db:drop', 'db:setup' ] + + desc "Retrieves the charset for the current environment's database" + task :charset => :environment do + config = ActiveRecord::Base.configurations[RAILS_ENV || 'development'] + case config['adapter'] + when 'mysql' + ActiveRecord::Base.establish_connection(config) + puts ActiveRecord::Base.connection.charset + when 'postgresql' + ActiveRecord::Base.establish_connection(config) + puts ActiveRecord::Base.connection.encoding + else + puts 'sorry, your database adapter is not supported yet, feel free to submit a patch' + end + end + + desc "Retrieves the collation for the current environment's database" + task :collation => :environment do + config = ActiveRecord::Base.configurations[RAILS_ENV || 'development'] + case config['adapter'] + when 'mysql' + ActiveRecord::Base.establish_connection(config) + puts ActiveRecord::Base.connection.collation + else + puts 'sorry, your database adapter is not supported yet, feel free to submit a patch' + end + end + + desc "Retrieves the current schema version number" + task :version => :environment do + puts "Current version: #{ActiveRecord::Migrator.current_version}" + end + + desc "Raises an error if there are pending migrations" + task :abort_if_pending_migrations => :environment do + if defined? ActiveRecord + pending_migrations = ActiveRecord::Migrator.new(:up, 'db/migrate').pending_migrations + + if pending_migrations.any? + puts "You have #{pending_migrations.size} pending migrations:" + pending_migrations.each do |pending_migration| + puts ' %4d %s' % [pending_migration.version, pending_migration.name] + end + abort %{Run "rake db:migrate" to update your database then try again.} + end + end + end + + desc 'Create the database, load the schema, and initialize with the seed data' + task :setup => [ 'db:create', 'db:schema:load', 'db:seed' ] + + desc 'Load the seed data from db/seeds.rb' + task :seed => :environment do + seed_file = File.join(Rails.root, 'db', 'seeds.rb') + load(seed_file) if File.exist?(seed_file) + end + + namespace :fixtures do + desc "Load fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures." + task :load => :environment do + require 'active_record/fixtures' + ActiveRecord::Base.establish_connection(Rails.env) + base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures') + fixtures_dir = ENV['FIXTURES_DIR'] ? File.join(base_dir, ENV['FIXTURES_DIR']) : base_dir + + (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/).map {|f| File.join(fixtures_dir, f) } : Dir.glob(File.join(fixtures_dir, '*.{yml,csv}'))).each do |fixture_file| + Fixtures.create_fixtures(File.dirname(fixture_file), File.basename(fixture_file, '.*')) + end + end + + desc "Search for a fixture given a LABEL or ID. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures." + task :identify => :environment do + require "active_record/fixtures" + + label, id = ENV["LABEL"], ENV["ID"] + raise "LABEL or ID required" if label.blank? && id.blank? + + puts %Q(The fixture ID for "#{label}" is #{Fixtures.identify(label)}.) if label + + base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures') + Dir["#{base_dir}/**/*.yml"].each do |file| + if data = YAML::load(ERB.new(IO.read(file)).result) + data.keys.each do |key| + key_id = Fixtures.identify(key) + + if key == label || key_id == id.to_i + puts "#{file}: #{key} (#{key_id})" + end + end + end + end + end + end + + namespace :schema do + desc "Create a db/schema.rb file that can be portably used against any DB supported by AR" + task :dump => :environment do + require 'active_record/schema_dumper' + File.open(ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb", "w") do |file| + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file) + end + Rake::Task["db:schema:dump"].reenable + end + + desc "Load a schema.rb file into the database" + task :load => :environment do + file = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb" + if File.exists?(file) + load(file) + else + abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to prevent active_record from loading: config.frameworks -= [ :active_record ]} + end + end + end + + namespace :structure do + desc "Dump the database structure to a SQL file" + task :dump => :environment do + abcs = ActiveRecord::Base.configurations + case abcs[RAILS_ENV]["adapter"] + 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" + ENV['PGHOST'] = abcs[RAILS_ENV]["host"] if abcs[RAILS_ENV]["host"] + ENV['PGPORT'] = abcs[RAILS_ENV]["port"].to_s if abcs[RAILS_ENV]["port"] + ENV['PGPASSWORD'] = abcs[RAILS_ENV]["password"].to_s if abcs[RAILS_ENV]["password"] + search_path = abcs[RAILS_ENV]["schema_search_path"] + unless search_path.blank? + search_path = search_path.split(",").map{|search_path| "--schema=#{search_path.strip}" }.join(" ") + end + `pg_dump -i -U "#{abcs[RAILS_ENV]["username"]}" -s -x -O -f db/#{RAILS_ENV}_structure.sql #{search_path} #{abcs[RAILS_ENV]["database"]}` + raise "Error dumping database" if $?.exitstatus == 1 + when "sqlite", "sqlite3" + dbfile = abcs[RAILS_ENV]["database"] || abcs[RAILS_ENV]["dbfile"] + `#{abcs[RAILS_ENV]["adapter"]} #{dbfile} .schema > db/#{RAILS_ENV}_structure.sql` + when "sqlserver" + `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /f db\\#{RAILS_ENV}_structure.sql /q /A /r` + `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /F db\ /q /A /r` + when "firebird" + set_firebird_env(abcs[RAILS_ENV]) + db_string = firebird_db_string(abcs[RAILS_ENV]) + sh "isql -a #{db_string} > #{Rails.root}/db/#{RAILS_ENV}_structure.sql" + else + raise "Task not supported by '#{abcs["test"]["adapter"]}'" + end + + if ActiveRecord::Base.connection.supports_migrations? + File.open("#{Rails.root}/db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information } + end + end + end + + namespace :test do + desc "Recreate the test database from the current schema.rb" + task :load => 'db:test:purge' do + ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test']) + ActiveRecord::Schema.verbose = false + Rake::Task["db:schema:load"].invoke + end + + desc "Recreate the test database from the current environment's database schema" + task :clone => %w(db:schema:dump db:test:load) + + desc "Recreate the test databases from the development structure" + task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do + abcs = ActiveRecord::Base.configurations + case abcs["test"]["adapter"] + 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| + ActiveRecord::Base.connection.execute(table) + end + when "postgresql" + ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"] + ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"] + ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"] + `psql -U "#{abcs["test"]["username"]}" -f #{Rails.root}/db/#{RAILS_ENV}_structure.sql #{abcs["test"]["database"]}` + when "sqlite", "sqlite3" + dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"] + `#{abcs["test"]["adapter"]} #{dbfile} < #{Rails.root}/db/#{RAILS_ENV}_structure.sql` + when "sqlserver" + `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql` + when "oci", "oracle" + ActiveRecord::Base.establish_connection(:test) + IO.readlines("#{Rails.root}/db/#{RAILS_ENV}_structure.sql").join.split(";\n\n").each do |ddl| + ActiveRecord::Base.connection.execute(ddl) + end + when "firebird" + set_firebird_env(abcs["test"]) + db_string = firebird_db_string(abcs["test"]) + sh "isql -i #{Rails.root}/db/#{RAILS_ENV}_structure.sql #{db_string}" + else + raise "Task not supported by '#{abcs["test"]["adapter"]}'" + end + end + + desc "Empty the test database" + task :purge => :environment do + abcs = ActiveRecord::Base.configurations + case abcs["test"]["adapter"] + when "mysql" + ActiveRecord::Base.establish_connection(:test) + ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"], abcs["test"]) + when "postgresql" + ActiveRecord::Base.clear_active_connections! + drop_database(abcs['test']) + create_database(abcs['test']) + when "sqlite","sqlite3" + dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"] + File.delete(dbfile) if File.exist?(dbfile) + when "sqlserver" + dropfkscript = "#{abcs["test"]["host"]}.#{abcs["test"]["database"]}.DP1".gsub(/\\/,'-') + `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{dropfkscript}` + `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql` + when "oci", "oracle" + ActiveRecord::Base.establish_connection(:test) + ActiveRecord::Base.connection.structure_drop.split(";\n\n").each do |ddl| + ActiveRecord::Base.connection.execute(ddl) + end + when "firebird" + ActiveRecord::Base.establish_connection(:test) + ActiveRecord::Base.connection.recreate_database! + else + raise "Task not supported by '#{abcs["test"]["adapter"]}'" + end + end + + desc 'Check for pending migrations and load the test schema' + task :prepare => 'db:abort_if_pending_migrations' do + if defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank? + Rake::Task[{ :sql => "db:test:clone_structure", :ruby => "db:test:load" }[ActiveRecord::Base.schema_format]].invoke + end + end + end + + namespace :sessions do + desc "Creates a sessions migration for use with ActiveRecord::SessionStore" + task :create => :environment do + raise "Task unavailable to this database (no migration support)" unless ActiveRecord::Base.connection.supports_migrations? + require 'rails/generators' + require 'rails/generators/rails/session_migration/session_migration_generator' + Rails::Generators::SessionMigrationGenerator.start [ ENV["MIGRATION"] || "add_sessions_table" ] + end + + desc "Clear the sessions table" + task :clear => :environment do + ActiveRecord::Base.connection.execute "DELETE FROM #{session_table_name}" + end + end +end + +def drop_database(config) + case config['adapter'] + when 'mysql' + ActiveRecord::Base.establish_connection(config) + ActiveRecord::Base.connection.drop_database config['database'] + when /^sqlite/ + require 'pathname' + path = Pathname.new(config['database']) + file = path.absolute? ? path.to_s : File.join(Rails.root, path) + + FileUtils.rm(file) + when 'postgresql' + ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public')) + ActiveRecord::Base.connection.drop_database config['database'] + end +end + +def session_table_name + ActiveRecord::SessionStore::Session.table_name +end + +def set_firebird_env(config) + ENV["ISC_USER"] = config["username"].to_s if config["username"] + ENV["ISC_PASSWORD"] = config["password"].to_s if config["password"] +end + +def firebird_db_string(config) + FireRuby::Database.db_string_for(config.symbolize_keys) +end -- cgit v1.2.3 From 1fbd02e44618f1f56e1d9d0af85c6a4108a66532 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Wed, 30 Dec 2009 19:33:32 -0800 Subject: Revert "Add config.action_controller.include behavior to plugins." This reverts commit 7e8b7f46bfc086a36db996420fbee93348c5268e. Conflicts: railties/lib/rails/plugin.rb --- activerecord/lib/active_record/rails.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/rails.rb b/activerecord/lib/active_record/rails.rb index 8bc49c3d96..a13bd2a5da 100644 --- a/activerecord/lib/active_record/rails.rb +++ b/activerecord/lib/active_record/rails.rb @@ -7,7 +7,6 @@ require "action_controller/rails" module ActiveRecord class Plugin < Rails::Plugin plugin_name :active_record - include_modules_in "ActiveRecord::Base" rake_tasks do load "active_record/railties/databases.rake" -- cgit v1.2.3 From d5f9173926598ace058b502e00c49f6477820509 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 1 Jan 2010 00:13:38 +0530 Subject: Add Relation#delete [Pratik Naik, Emilio Tagua] --- .../associations/has_and_belongs_to_many_association.rb | 2 +- activerecord/lib/active_record/base.rb | 6 +++--- activerecord/lib/active_record/relation.rb | 4 ++++ 3 files changed, 8 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index 9569b0c6f9..5913189c98 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -73,7 +73,7 @@ module ActiveRecord relation = arel_table(@reflection.options[:join_table]) relation.where(relation[@reflection.primary_key_name].eq(@owner.id). and(Arel::Predicates::In.new(relation[@reflection.association_foreign_key], records.map(&:id))) - ).delete + ).delete_all end end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index c4bdbdad08..de320475e8 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -815,8 +815,8 @@ module ActiveRecord #:nodoc: # # # Delete multiple rows # Todo.delete([2,3,4]) - def delete(id) - delete_all([ "#{connection.quote_column_name(primary_key)} IN (?)", id ]) + def delete(id_or_array) + arel_table.where(construct_conditions(nil, scope(:find))).delete(id_or_array) end # Destroy an object (or multiple objects) that has the given id, the object is instantiated first, @@ -2323,7 +2323,7 @@ module ActiveRecord #:nodoc: # be made (since they can't be persisted). def destroy unless new_record? - self.class.arel_table.where(self.class.arel_table[self.class.primary_key].eq(id)).delete + self.class.arel_table.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all end @destroyed = true diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index ae03e1d7e9..502aa909ab 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -109,6 +109,10 @@ module ActiveRecord @relation.delete.tap { reset } end + def delete(id_or_array) + where(@klass.primary_key => id_or_array).delete_all + end + def loaded? @loaded end -- cgit v1.2.3 From 93555c672e3edd5705eb381f2bf3d4f93cffa96f Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 1 Jan 2010 00:56:49 +0530 Subject: Add Relation#table to get the relevant Arel::Table --- activerecord/lib/active_record/relation.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 502aa909ab..6a1237cdd1 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -6,7 +6,7 @@ module ActiveRecord delegate :length, :collect, :map, :each, :all?, :to => :to_a attr_reader :relation, :klass, :preload_associations, :eager_load_associations - attr_writer :readonly, :preload_associations, :eager_load_associations + attr_writer :readonly, :preload_associations, :eager_load_associations, :table def initialize(klass, relation) @klass, @relation = klass, relation @@ -133,9 +133,18 @@ module ActiveRecord relation.readonly = @readonly relation.preload_associations = @preload_associations relation.eager_load_associations = @eager_load_associations + relation.table = table relation end + def table + @table ||= Arel::Table.new(@klass.table_name, Arel::Sql::Engine.new(@klass)) + end + + def primary_key + @primary_key ||= table[@klass.primary_key] + end + protected def method_missing(method, *args, &block) -- cgit v1.2.3 From 783caae52f3c00975490cd40dc849f648c8fa9e1 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 1 Jan 2010 01:00:18 +0530 Subject: Use Arel::Attribute for pk conditions --- activerecord/lib/active_record/relation/finder_methods.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 7a1d6fc538..c3e5f27838 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -21,8 +21,8 @@ module ActiveRecord end def exists?(id = nil) - relation = select("#{@klass.quoted_table_name}.#{@klass.primary_key}").limit(1) - relation = relation.where(@klass.primary_key => id) if id + relation = select(primary_key).limit(1) + relation = relation.where(primary_key.eq(id)) if id relation.first ? true : false end @@ -78,7 +78,7 @@ module ActiveRecord end def find_one(id) - record = where(@klass.primary_key => id).first + record = where(primary_key.eq(id)).first unless record conditions = where_clause(', ') @@ -90,7 +90,7 @@ module ActiveRecord end def find_some(ids) - result = where(@klass.primary_key => ids).all + result = where(primary_key.in(ids)).all expected_size = if @relation.taken && ids.size > @relation.taken -- cgit v1.2.3 From a23f4b6aeed6b49f90f5c544c818798840f090b7 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Thu, 31 Dec 2009 11:46:56 -0800 Subject: Add a /rails.rb for each framework for consistency --- activerecord/lib/active_record/rails.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/rails.rb b/activerecord/lib/active_record/rails.rb index a13bd2a5da..8e9ba041fd 100644 --- a/activerecord/lib/active_record/rails.rb +++ b/activerecord/lib/active_record/rails.rb @@ -2,6 +2,7 @@ # rails, so let's make sure that it gets required before # here. This is needed for correctly setting up the middleware. # In the future, this might become an optional require. +require "active_record" require "action_controller/rails" module ActiveRecord -- cgit v1.2.3 From fdc62cdcdb70e13f2cf9e6dced35016716d889f9 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 1 Jan 2010 02:17:42 +0530 Subject: Use Arel::Attribute when building where conditions from hash --- .../lib/active_record/relation/query_methods.rb | 41 +++++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 631c80da25..dd097db632 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -104,14 +104,16 @@ module ActiveRecord def where(*args) return spawn if args.blank? - if [String, Hash, Array].include?(args.first.class) - conditions = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first) - conditions = Arel::SqlLiteral.new(conditions) if conditions + 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 + elsif args.first.is_a?(Hash) + build_predicate_from_hash(args.first) else - conditions = args.first + args.first end - spawn(@relation.where(conditions)) + spawn(@relation.where(*conditions)) end private @@ -128,5 +130,34 @@ module ActiveRecord }.join(',') end + def build_predicate_from_hash(attributes, default_table = self.table) + attributes = @klass.send(:expand_hash_conditions_for_aggregates, attributes) + + predicates = attributes.map do |column, value| + arel_table = default_table + + if value.is_a?(Hash) + arel_table = Arel::Table.new(column, Arel::Sql::Engine.new(@klass)) + build_predicate_from_hash(value, arel_table) + else + column = column.to_s + + if column.include?('.') + table_name, column = column.split('.', 2) + arel_table = Arel::Table.new(table_name, Arel::Sql::Engine.new(@klass)) + end + + case value + when Array, Range, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope + arel_table[column].in(value) + else + arel_table[column].eq(value) + end + end + end + + predicates.flatten + end + end end -- cgit v1.2.3 From e749424dfa38a0300a621b772eae96f9cc5d2555 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Thu, 31 Dec 2009 12:28:48 -0800 Subject: Rename rails.rb -> rails/all.rb and rails/core.rb -> rails.rb --- activerecord/lib/active_record/rails.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/rails.rb b/activerecord/lib/active_record/rails.rb index 8e9ba041fd..f628c3ace0 100644 --- a/activerecord/lib/active_record/rails.rb +++ b/activerecord/lib/active_record/rails.rb @@ -4,6 +4,7 @@ # In the future, this might become an optional require. require "active_record" require "action_controller/rails" +require "rails" module ActiveRecord class Plugin < Rails::Plugin -- cgit v1.2.3 From ae7ada1fde1648a5c81b9e6adf74d515e0bbe0a5 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Thu, 31 Dec 2009 13:11:54 -0800 Subject: Some railties cleanup: * Rename /rails.rb -> /railtie.rb * Rails::Plugin -> Rails::Railtie * Rails::Plugin::Vendored -> Rails::Plugin --- activerecord/lib/active_record/rails.rb | 71 ------------------------------- activerecord/lib/active_record/railtie.rb | 71 +++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 71 deletions(-) delete mode 100644 activerecord/lib/active_record/rails.rb create mode 100644 activerecord/lib/active_record/railtie.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/rails.rb b/activerecord/lib/active_record/rails.rb deleted file mode 100644 index f628c3ace0..0000000000 --- a/activerecord/lib/active_record/rails.rb +++ /dev/null @@ -1,71 +0,0 @@ -# For now, action_controller must always be present with -# rails, so let's make sure that it gets required before -# here. This is needed for correctly setting up the middleware. -# In the future, this might become an optional require. -require "active_record" -require "action_controller/rails" -require "rails" - -module ActiveRecord - class Plugin < Rails::Plugin - plugin_name :active_record - - rake_tasks do - load "active_record/railties/databases.rake" - end - - initializer "active_record.set_configs" do |app| - app.config.active_record.each do |k,v| - ActiveRecord::Base.send "#{k}=", v - end - end - - # This sets the database configuration from Configuration#database_configuration - # and then establishes the connection. - initializer "active_record.initialize_database" do |app| - ActiveRecord::Base.configurations = app.config.database_configuration - ActiveRecord::Base.establish_connection - end - - initializer "active_record.initialize_timezone" do - ActiveRecord::Base.time_zone_aware_attributes = true - ActiveRecord::Base.default_timezone = :utc - end - - # Expose database runtime to controller for logging. - initializer "active_record.log_runtime" do |app| - require "active_record/railties/controller_runtime" - ActionController::Base.send :include, ActiveRecord::Railties::ControllerRuntime - end - - # Setup database middleware after initializers have run - initializer "active_record.initialize_database_middleware" do |app| - middleware = app.config.middleware - if middleware.include?(ActiveRecord::SessionStore) - middleware.insert_before ActiveRecord::SessionStore, ActiveRecord::ConnectionAdapters::ConnectionManagement - middleware.insert_before ActiveRecord::SessionStore, ActiveRecord::QueryCache - else - middleware.use ActiveRecord::ConnectionAdapters::ConnectionManagement - middleware.use ActiveRecord::QueryCache - end - end - - initializer "active_record.load_observers" do - ActiveRecord::Base.instantiate_observers - end - - # TODO: ActiveRecord::Base.logger should delegate to its own config.logger - initializer "active_record.logger" do - ActiveRecord::Base.logger ||= ::Rails.logger - end - - initializer "active_record.notifications" do - require 'active_support/notifications' - - ActiveSupport::Notifications.subscribe("sql") do |name, before, after, instrumenter_id, payload| - ActiveRecord::Base.connection.log_info(payload[:sql], payload[:name], (after - before) * 1000) - end - end - - end -end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb new file mode 100644 index 0000000000..657ee738c0 --- /dev/null +++ b/activerecord/lib/active_record/railtie.rb @@ -0,0 +1,71 @@ +# For now, action_controller must always be present with +# rails, so let's make sure that it gets required before +# here. This is needed for correctly setting up the middleware. +# In the future, this might become an optional require. +require "active_record" +require "action_controller/railtie" +require "rails" + +module ActiveRecord + class Railtie < Rails::Railtie + plugin_name :active_record + + rake_tasks do + load "active_record/railties/databases.rake" + end + + initializer "active_record.set_configs" do |app| + app.config.active_record.each do |k,v| + ActiveRecord::Base.send "#{k}=", v + end + end + + # This sets the database configuration from Configuration#database_configuration + # and then establishes the connection. + initializer "active_record.initialize_database" do |app| + ActiveRecord::Base.configurations = app.config.database_configuration + ActiveRecord::Base.establish_connection + end + + initializer "active_record.initialize_timezone" do + ActiveRecord::Base.time_zone_aware_attributes = true + ActiveRecord::Base.default_timezone = :utc + end + + # Expose database runtime to controller for logging. + initializer "active_record.log_runtime" do |app| + require "active_record/railties/controller_runtime" + ActionController::Base.send :include, ActiveRecord::Railties::ControllerRuntime + end + + # Setup database middleware after initializers have run + initializer "active_record.initialize_database_middleware" do |app| + middleware = app.config.middleware + if middleware.include?(ActiveRecord::SessionStore) + middleware.insert_before ActiveRecord::SessionStore, ActiveRecord::ConnectionAdapters::ConnectionManagement + middleware.insert_before ActiveRecord::SessionStore, ActiveRecord::QueryCache + else + middleware.use ActiveRecord::ConnectionAdapters::ConnectionManagement + middleware.use ActiveRecord::QueryCache + end + end + + initializer "active_record.load_observers" do + ActiveRecord::Base.instantiate_observers + end + + # TODO: ActiveRecord::Base.logger should delegate to its own config.logger + initializer "active_record.logger" do + ActiveRecord::Base.logger ||= ::Rails.logger + end + + initializer "active_record.notifications" do + require 'active_support/notifications' + + ActiveSupport::Notifications.subscribe("sql") do |name, before, after, instrumenter_id, payload| + ActiveRecord::Base.connection.log_info(payload[:sql], payload[:name], (after - before) * 1000) + end + end + + end +end -- cgit v1.2.3 From 3c23b71a09c28a7be13090f83161963b56a5088e Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 1 Jan 2010 03:14:19 +0530 Subject: Move predicate building to a stand alone PredicateBuilder class --- activerecord/lib/active_record.rb | 1 + .../active_record/relation/predicate_builder.rb | 36 ++++++++++++++++++++++ .../lib/active_record/relation/query_methods.rb | 34 +++----------------- 3 files changed, 41 insertions(+), 30 deletions(-) create mode 100644 activerecord/lib/active_record/relation/predicate_builder.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index a524dc50a1..cf439b0dc0 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -54,6 +54,7 @@ module ActiveRecord autoload :QueryMethods autoload :FinderMethods autoload :CalculationMethods + autoload :PredicateBuilder end autoload :Base diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb new file mode 100644 index 0000000000..c85a0510b4 --- /dev/null +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -0,0 +1,36 @@ +module ActiveRecord + class PredicateBuilder + + def initialize(engine) + @engine = engine + end + + def build_from_hash(attributes, default_table) + predicates = attributes.map do |column, value| + arel_table = default_table + + if value.is_a?(Hash) + arel_table = Arel::Table.new(column, @engine) + build_predicate_from_hash(value, arel_table) + else + column = column.to_s + + if column.include?('.') + table_name, column = column.split('.', 2) + arel_table = Arel::Table.new(table_name, @engine) + end + + case value + when Array, Range, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope + arel_table[column].in(value) + else + arel_table[column].eq(value) + end + end + end + + predicates.flatten + end + + end +end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index dd097db632..432b33e174 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -104,11 +104,14 @@ module ActiveRecord def where(*args) return spawn if args.blank? + builder = PredicateBuilder.new(Arel::Sql::Engine.new(@klass)) + 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 elsif args.first.is_a?(Hash) - build_predicate_from_hash(args.first) + attributes = @klass.send(:expand_hash_conditions_for_aggregates, args.first) + builder.build_from_hash(attributes, table) else args.first end @@ -130,34 +133,5 @@ module ActiveRecord }.join(',') end - def build_predicate_from_hash(attributes, default_table = self.table) - attributes = @klass.send(:expand_hash_conditions_for_aggregates, attributes) - - predicates = attributes.map do |column, value| - arel_table = default_table - - if value.is_a?(Hash) - arel_table = Arel::Table.new(column, Arel::Sql::Engine.new(@klass)) - build_predicate_from_hash(value, arel_table) - else - column = column.to_s - - if column.include?('.') - table_name, column = column.split('.', 2) - arel_table = Arel::Table.new(table_name, Arel::Sql::Engine.new(@klass)) - end - - case value - when Array, Range, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope - arel_table[column].in(value) - else - arel_table[column].eq(value) - end - end - end - - predicates.flatten - end - end end -- cgit v1.2.3 From 141d3aff2f010384cc5d3488f75605bd0f702416 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 1 Jan 2010 03:17:47 +0530 Subject: Fix the method name for recusion --- activerecord/lib/active_record/relation/predicate_builder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index c85a0510b4..223eb71885 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -11,7 +11,7 @@ module ActiveRecord if value.is_a?(Hash) arel_table = Arel::Table.new(column, @engine) - build_predicate_from_hash(value, arel_table) + build_from_hash(value, arel_table) else column = column.to_s -- cgit v1.2.3 From 77c23b2104d62ab1cf1fb5808fef14e38d094605 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 1 Jan 2010 04:14:34 +0530 Subject: Use PredicateBuilder for sql hash sanitization --- activerecord/lib/active_record/associations.rb | 2 +- activerecord/lib/active_record/base.rb | 32 ++++++---------------- .../active_record/relation/predicate_builder.rb | 6 ++-- 3 files changed, 14 insertions(+), 26 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index f0bad6c3ba..052197f7ad 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1488,7 +1488,7 @@ module ActiveRecord dependent_conditions = [] dependent_conditions << "#{reflection.primary_key_name} = \#{record.#{reflection.name}.send(:owner_quoted_id)}" dependent_conditions << "#{reflection.options[:as]}_type = '#{base_class.name}'" if reflection.options[:as] - dependent_conditions << sanitize_sql(reflection.options[:conditions], reflection.quoted_table_name) if reflection.options[:conditions] + dependent_conditions << sanitize_sql(reflection.options[:conditions], reflection.table_name) if reflection.options[:conditions] dependent_conditions << extra_conditions if extra_conditions dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ") dependent_conditions = dependent_conditions.gsub('@', '\@') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index de320475e8..fb160dea9a 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1508,6 +1508,10 @@ module ActiveRecord #:nodoc: Relation.new(self, Arel::Table.new(table || table_name)) end + def engine + @engine ||= Arel::Sql::Engine.new(self) + end + private # Finder methods must instantiate through this method to work with the # single-table inheritance model that makes it possible to create @@ -1964,7 +1968,7 @@ module ActiveRecord #:nodoc: # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'" # { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'" # "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'" - def sanitize_sql_for_conditions(condition, table_name = quoted_table_name) + def sanitize_sql_for_conditions(condition, table_name = self.table_name) return nil if condition.blank? case condition @@ -2035,30 +2039,12 @@ module ActiveRecord #:nodoc: # And for value objects on a composed_of relationship: # { :address => Address.new("123 abc st.", "chicago") } # # => "address_street='123 abc st.' and address_city='chicago'" - def sanitize_sql_hash_for_conditions(attrs, default_table_name = quoted_table_name) + def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name) attrs = expand_hash_conditions_for_aggregates(attrs) - conditions = attrs.map do |attr, value| - table_name = default_table_name - - unless value.is_a?(Hash) - attr = attr.to_s - - # Extract table name from qualified attribute names. - if attr.include?('.') - attr_table_name, attr = attr.split('.', 2) - attr_table_name = connection.quote_table_name(attr_table_name) - else - attr_table_name = table_name - end - - attribute_condition("#{attr_table_name}.#{connection.quote_column_name(attr)}", value) - else - sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s)) - end - end.join(' AND ') - - replace_bind_variables(conditions, expand_range_bind_variables(attrs.values)) + table = Arel::Table.new(default_table_name, engine) + builder = PredicateBuilder.new(engine) + builder.build_from_hash(attrs, table).map(&:to_sql).join(' AND ') end alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 223eb71885..478d2cb597 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -20,11 +20,13 @@ module ActiveRecord arel_table = Arel::Table.new(table_name, @engine) end + attribute = Arel::Attribute.new(arel_table, column.to_sym) + case value when Array, Range, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope - arel_table[column].in(value) + attribute.in(value) else - arel_table[column].eq(value) + attribute.eq(value) end end end -- cgit v1.2.3 From c62e88a5ab7bc0a70e36dceda2a0f22995a5d087 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 1 Jan 2010 04:14:58 +0530 Subject: Try using cached attribute before creating a new one --- activerecord/lib/active_record/relation/predicate_builder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 478d2cb597..08840d102a 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -20,7 +20,7 @@ module ActiveRecord arel_table = Arel::Table.new(table_name, @engine) end - attribute = Arel::Attribute.new(arel_table, column.to_sym) + attribute = arel_table[column] || Arel::Attribute.new(arel_table, column.to_sym) case value when Array, Range, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope -- cgit v1.2.3 From 8a32d3796778fc1f151a2259fa3b32b9d3c8d56b Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 1 Jan 2010 04:33:56 +0530 Subject: Handle Range with excluded end --- activerecord/lib/active_record/relation/predicate_builder.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 08840d102a..d5e0c90184 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -23,8 +23,15 @@ module ActiveRecord attribute = arel_table[column] || Arel::Attribute.new(arel_table, column.to_sym) case value - when Array, Range, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope + when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope attribute.in(value) + when Range + # TODO : Arel should handle ranges with excluded end. + if value.exclude_end? + [attribute.gteq(value.begin), attribute.lt(value.end)] + else + attribute.in(value) + end else attribute.eq(value) end -- cgit v1.2.3 From 58ad6567692ee44b91bd05833b6cb6c454481e19 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 1 Jan 2010 04:37:19 +0530 Subject: Make sure association proxy does not pass quoted table name to sanitize_sql --- activerecord/lib/active_record/associations/association_proxy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 7d8f4670fa..022dd2ae9b 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -161,7 +161,7 @@ module ActiveRecord end # Forwards the call to the reflection class. - def sanitize_sql(sql, table_name = @reflection.klass.quoted_table_name) + def sanitize_sql(sql, table_name = @reflection.klass.table_name) @reflection.klass.send(:sanitize_sql, sql, table_name) end -- cgit v1.2.3 From d200d080041bc4cefba8a8d39943b233ee2630a0 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 1 Jan 2010 23:15:48 +0530 Subject: Use arel for building the STI type condition --- activerecord/lib/active_record/base.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index fb160dea9a..36b46eee5e 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1681,14 +1681,14 @@ module ActiveRecord #:nodoc: o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)} end - def type_condition(table_alias=nil) - quoted_table_alias = self.connection.quote_table_name(table_alias || table_name) - quoted_inheritance_column = connection.quote_column_name(inheritance_column) - type_condition = subclasses.inject("#{quoted_table_alias}.#{quoted_inheritance_column} = '#{sti_name}' " ) do |condition, subclass| - condition << "OR #{quoted_table_alias}.#{quoted_inheritance_column} = '#{subclass.sti_name}' " - end + def type_condition(table_alias = nil) + table = Arel::Table.new(table_name, :engine => engine, :as => table_alias) + + sti_column = table[inheritance_column] + condition = sti_column.eq(sti_name) + subclasses.each{|subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) } - " (#{type_condition}) " + condition.to_sql end # Guesses the table name, but does not decorate it with prefix and suffix information. -- cgit v1.2.3 From fc94c03c1d509eda7f87ab216e8d1115afbfd1ed Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 1 Jan 2010 23:34:15 +0530 Subject: Remove unncessary arguments passed to arel_table --- activerecord/lib/active_record/associations.rb | 3 ++- activerecord/lib/active_record/locking/optimistic.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 052197f7ad..dc1fa08941 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1751,7 +1751,7 @@ module ActiveRecord def construct_finder_sql_for_association_limiting(options, join_dependency) scope = scope(:find) - relation = arel_table(options[:from]) + relation = arel_table for association in join_dependency.join_associations relation = association.join_relation(relation) @@ -1764,6 +1764,7 @@ module ActiveRecord order(construct_order(options[:order], scope)). limit(construct_limit(options[:limit], scope)). offset(construct_limit(options[:offset], scope)). + from(options[:from]). select(connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", construct_order(options[:order], scope(:find)).join(","))) relation.to_sql diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 986bc7009b..7911370332 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -78,7 +78,7 @@ module ActiveRecord attribute_names.uniq! begin - arel_table = self.class.arel_table(self.class.table_name) + arel_table = self.class.arel_table affected_rows = arel_table.where( arel_table[self.class.primary_key].eq(quoted_id).and( -- cgit v1.2.3 From 5971842d2d6c62831d86530e6fe9917495772969 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 1 Jan 2010 23:42:13 +0530 Subject: Use Arel::Table instead of ActiveRecord::Relation from HABTM and has_many#delete_records --- .../associations/has_and_belongs_to_many_association.rb | 6 +++--- activerecord/lib/active_record/associations/has_many_association.rb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index 5913189c98..bd05d1014c 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -44,7 +44,7 @@ module ActiveRecord if @reflection.options[:insert_sql] @owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record)) else - relation = arel_table(@reflection.options[:join_table]) + relation = Arel::Table.new(@reflection.options[:join_table]) attributes = columns.inject({}) do |attrs, column| case column.name.to_s when @reflection.primary_key_name.to_s @@ -70,10 +70,10 @@ module ActiveRecord if sql = @reflection.options[:delete_sql] records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) } else - relation = arel_table(@reflection.options[:join_table]) + relation = Arel::Table.new(@reflection.options[:join_table]) relation.where(relation[@reflection.primary_key_name].eq(@owner.id). and(Arel::Predicates::In.new(relation[@reflection.association_foreign_key], records.map(&:id))) - ).delete_all + ).delete end end diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index be74ddfcf0..d3336cf2d2 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -69,7 +69,7 @@ module ActiveRecord when :delete_all @reflection.klass.delete(records.map { |record| record.id }) else - relation = arel_table(@reflection.table_name) + relation = Arel::Table.new(@reflection.table_name) relation.where(relation[@reflection.primary_key_name].eq(@owner.id). and(Arel::Predicates::In.new(relation[@reflection.klass.primary_key], records.map(&:id))) ).update(relation[@reflection.primary_key_name] => nil) -- cgit v1.2.3 From d2c4b3b672d52cddbc214e0fe5175cc92ca76f92 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 2 Jan 2010 00:03:20 +0530 Subject: Rename Model.engine to active_relation_engine. Cache arel_table and the method takes no arguments now --- activerecord/lib/active_record/base.rb | 21 +++++++++++++-------- activerecord/lib/active_record/named_scope.rb | 2 +- 2 files changed, 14 insertions(+), 9 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 36b46eee5e..d12397d5c0 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1391,7 +1391,8 @@ module ActiveRecord #:nodoc: # end def reset_column_information undefine_attribute_methods - @arel_table = @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil + @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil + @arel_table = @active_relation_engine = nil end def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc: @@ -1504,12 +1505,16 @@ module ActiveRecord #:nodoc: "(#{segments.join(') AND (')})" unless segments.empty? end - def arel_table(table = nil) - Relation.new(self, Arel::Table.new(table || table_name)) + def arel_table + @arel_table ||= Relation.new(self, active_relation_table) end - def engine - @engine ||= Arel::Sql::Engine.new(self) + def active_relation_table(table_name_alias = nil) + Arel::Table.new(table_name, :as => table_name_alias) + end + + def active_relation_engine + @active_relation_engine ||= Arel::Sql::Engine.new(self) end private @@ -1682,7 +1687,7 @@ module ActiveRecord #:nodoc: end def type_condition(table_alias = nil) - table = Arel::Table.new(table_name, :engine => engine, :as => table_alias) + table = Arel::Table.new(table_name, :engine => active_relation_engine, :as => table_alias) sti_column = table[inheritance_column] condition = sti_column.eq(sti_name) @@ -2042,8 +2047,8 @@ module ActiveRecord #:nodoc: def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name) attrs = expand_hash_conditions_for_aggregates(attrs) - table = Arel::Table.new(default_table_name, engine) - builder = PredicateBuilder.new(engine) + table = Arel::Table.new(default_table_name, active_relation_engine) + builder = PredicateBuilder.new(active_relation_engine) builder.build_from_hash(attrs, table).map(&:to_sql).join(' AND ') end alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index a6336e762a..02ee2d1401 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -27,7 +27,7 @@ module ActiveRecord Scope.new(self, options, &block) else unless scoped?(:find) - finder_needs_type_condition? ? arel_table.where(type_condition) : arel_table + finder_needs_type_condition? ? arel_table.where(type_condition) : arel_table.spawn else construct_finder_arel_with_includes end -- cgit v1.2.3 From b35873a575586d31e9de681aec31d4b884b25aba Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 2 Jan 2010 00:25:29 +0530 Subject: Rename Model.arel_table to Model.active_relation --- activerecord/lib/active_record/associations.rb | 4 ++-- activerecord/lib/active_record/base.rb | 26 +++++++++++----------- activerecord/lib/active_record/calculations.rb | 4 ++-- .../lib/active_record/locking/optimistic.rb | 8 +++---- activerecord/lib/active_record/named_scope.rb | 2 +- .../active_record/relation/calculation_methods.rb | 4 ++-- .../active_record/relation/predicate_builder.rb | 10 ++++----- 7 files changed, 29 insertions(+), 29 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index dc1fa08941..80e6acf34c 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1706,7 +1706,7 @@ module ActiveRecord def construct_finder_arel_with_included_associations(options, join_dependency) scope = scope(:find) - relation = arel_table + relation = active_relation for association in join_dependency.join_associations relation = association.join_relation(relation) @@ -1751,7 +1751,7 @@ module ActiveRecord def construct_finder_sql_for_association_limiting(options, join_dependency) scope = scope(:find) - relation = arel_table + relation = active_relation for association in join_dependency.join_associations relation = association.join_relation(relation) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index d12397d5c0..7cefba2b82 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -816,7 +816,7 @@ module ActiveRecord #:nodoc: # # Delete multiple rows # Todo.delete([2,3,4]) def delete(id_or_array) - arel_table.where(construct_conditions(nil, scope(:find))).delete(id_or_array) + active_relation.where(construct_conditions(nil, scope(:find))).delete(id_or_array) end # Destroy an object (or multiple objects) that has the given id, the object is instantiated first, @@ -873,7 +873,7 @@ module ActiveRecord #:nodoc: def update_all(updates, conditions = nil, options = {}) scope = scope(:find) - relation = arel_table + relation = active_relation if conditions = construct_conditions(conditions, scope) relation = relation.where(Arel::SqlLiteral.new(conditions)) @@ -938,7 +938,7 @@ module ActiveRecord #:nodoc: # Both calls delete the affected posts all at once with a single DELETE statement. If you need to destroy dependent # associations or call your before_* or +after_destroy+ callbacks, use the +destroy_all+ method instead. def delete_all(conditions = nil) - arel_table.where(construct_conditions(conditions, scope(:find))).delete_all + active_relation.where(construct_conditions(conditions, scope(:find))).delete_all end # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part. @@ -1392,7 +1392,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_table = @active_relation_engine = nil + @active_relation = @active_relation_engine = nil end def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc: @@ -1505,8 +1505,8 @@ module ActiveRecord #:nodoc: "(#{segments.join(') AND (')})" unless segments.empty? end - def arel_table - @arel_table ||= Relation.new(self, active_relation_table) + def active_relation + @active_relation ||= Relation.new(self, active_relation_table) end def active_relation_table(table_name_alias = nil) @@ -1570,7 +1570,7 @@ module ActiveRecord #:nodoc: def construct_finder_arel(options = {}, scope = scope(:find)) validate_find_options(options) - relation = arel_table. + relation = active_relation. joins(construct_join(options[:joins], scope)). where(construct_conditions(options[:conditions], scope)). select(options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))). @@ -1671,7 +1671,7 @@ module ActiveRecord #:nodoc: def build_association_joins(joins) join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, joins, nil) - relation = arel_table.relation + relation = active_relation.relation join_dependency.join_associations.map { |association| if (association_relation = association.relation).is_a?(Array) [Arel::InnerJoin.new(relation, association_relation.first, association.association_join.first).joins(relation), @@ -2314,7 +2314,7 @@ module ActiveRecord #:nodoc: # be made (since they can't be persisted). def destroy unless new_record? - self.class.arel_table.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all + self.class.active_relation.where(self.class.active_relation[self.class.primary_key].eq(id)).delete_all end @destroyed = true @@ -2601,7 +2601,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.arel_table.where(self.class.arel_table[self.class.primary_key].eq(id)).update(attributes_with_values) + self.class.active_relation.where(self.class.active_relation[self.class.primary_key].eq(id)).update(attributes_with_values) end # Creates a record with values matching those of the instance attributes @@ -2614,9 +2614,9 @@ module ActiveRecord #:nodoc: attributes_values = arel_attributes_values new_id = if attributes_values.empty? - self.class.arel_table.insert connection.empty_insert_statement_value + self.class.active_relation.insert connection.empty_insert_statement_value else - self.class.arel_table.insert attributes_values + self.class.active_relation.insert attributes_values end self.id ||= new_id @@ -2711,7 +2711,7 @@ module ActiveRecord #:nodoc: if value && ((self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time))) || value.is_a?(Hash) || value.is_a?(Array)) value = value.to_yaml end - attrs[self.class.arel_table[name]] = value + attrs[self.class.active_relation[name]] = value end end end diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index d51d9f2159..20d287faeb 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -162,7 +162,7 @@ module ActiveRecord join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, includes, construct_join(options[:joins], scope)) construct_calculation_arel_with_included_associations(options, join_dependency) else - arel_table. + active_relation. joins(construct_join(options[:joins], scope)). from((scope && scope[:from]) || options[:from]). where(construct_conditions(options[:conditions], scope)). @@ -178,7 +178,7 @@ module ActiveRecord def construct_calculation_arel_with_included_associations(options, join_dependency) scope = scope(:find) - relation = arel_table + relation = active_relation for association in join_dependency.join_associations relation = association.join_relation(relation) diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 7911370332..f9e538c586 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -78,11 +78,11 @@ module ActiveRecord attribute_names.uniq! begin - arel_table = self.class.arel_table + relation = self.class.active_relation - affected_rows = arel_table.where( - arel_table[self.class.primary_key].eq(quoted_id).and( - arel_table[self.class.locking_column].eq(quote_value(previous_value)) + affected_rows = relation.where( + relation[self.class.primary_key].eq(quoted_id).and( + relation[self.class.locking_column].eq(quote_value(previous_value)) ) ).update(arel_attributes_values(false, false, attribute_names)) diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 02ee2d1401..5959265d5e 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -27,7 +27,7 @@ module ActiveRecord Scope.new(self, options, &block) else unless scoped?(:find) - finder_needs_type_condition? ? arel_table.where(type_condition) : arel_table.spawn + finder_needs_type_condition? ? active_relation.where(type_condition) : active_relation.spawn else construct_finder_arel_with_includes end diff --git a/activerecord/lib/active_record/relation/calculation_methods.rb b/activerecord/lib/active_record/relation/calculation_methods.rb index a925a99464..5246c7bc5d 100644 --- a/activerecord/lib/active_record/relation/calculation_methods.rb +++ b/activerecord/lib/active_record/relation/calculation_methods.rb @@ -53,7 +53,7 @@ module ActiveRecord def execute_simple_calculation(operation, column_name, distinct) #:nodoc: column = if @klass.column_names.include?(column_name.to_s) - Arel::Attribute.new(@klass.arel_table, column_name) + Arel::Attribute.new(@klass.active_relation, column_name) else Arel::SqlLiteral.new(column_name == :all ? "*" : column_name.to_s) end @@ -77,7 +77,7 @@ module ActiveRecord select_statement = if operation == 'count' && column_name == :all "COUNT(*) AS count_all" else - Arel::Attribute.new(@klass.arel_table, column_name).send(operation).as(aggregate_alias).to_sql + Arel::Attribute.new(@klass.active_relation, column_name).send(operation).as(aggregate_alias).to_sql end select_statement << ", #{group_field} AS #{group_alias}" diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index d5e0c90184..6b7d941350 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -7,20 +7,20 @@ module ActiveRecord def build_from_hash(attributes, default_table) predicates = attributes.map do |column, value| - arel_table = default_table + table = default_table if value.is_a?(Hash) - arel_table = Arel::Table.new(column, @engine) - build_from_hash(value, arel_table) + table = Arel::Table.new(column, :engine => @engine) + build_from_hash(value, table) else column = column.to_s if column.include?('.') table_name, column = column.split('.', 2) - arel_table = Arel::Table.new(table_name, @engine) + table = Arel::Table.new(table_name, :engine => @engine) end - attribute = arel_table[column] || Arel::Attribute.new(arel_table, column.to_sym) + attribute = table[column] || Arel::Attribute.new(table, column.to_sym) case value when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope -- cgit v1.2.3 From 498fddc714f66d5f6f9152910853ce355f059397 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 2 Jan 2010 03:20:52 +0530 Subject: Fix join string for the WHERE clause --- activerecord/lib/active_record/relation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 6a1237cdd1..8756695d46 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -166,7 +166,7 @@ module ActiveRecord end end - def where_clause(join_string = "\n\tAND ") + def where_clause(join_string = " AND ") @relation.send(:where_clauses).join(join_string) end -- cgit v1.2.3 From 4a7a14b0e15e07dbc4e49cfdd0694c17f81cfad0 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 2 Jan 2010 03:46:08 +0530 Subject: Use relations to build uniqueness conditions --- .../lib/active_record/validations/uniqueness.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index ffbe1b5c40..7efd312357 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -8,24 +8,25 @@ module ActiveRecord def validate_each(record, attribute, value) finder_class = find_finder_class_for(record) + table = finder_class.active_relation + table_name = record.class.quoted_table_name sql, params = mount_sql_and_params(finder_class, table_name, attribute, value) + relation = table.where(sql, *params) + Array(options[:scope]).each do |scope_item| scope_value = record.send(scope_item) - sql << " AND " << record.class.send(:attribute_condition, "#{table_name}.#{scope_item}", scope_value) - params << scope_value + relation = relation.where(scope_item => scope_value) end unless record.new_record? - sql << " AND #{record.class.quoted_table_name}.#{record.class.primary_key} <> ?" - params << record.send(:id) + # TODO : This should be in Arel + relation = relation.where("#{record.class.quoted_table_name}.#{record.class.primary_key} <> ?", record.send(:id)) end - finder_class.send(:with_exclusive_scope) do - if finder_class.exists?([sql, *params]) - record.errors.add(attribute, :taken, :default => options[:message], :value => value) - end + if relation.exists? + record.errors.add(attribute, :taken, :default => options[:message], :value => value) end end -- cgit v1.2.3 From 7ff131f740d10cad767ab1200f9d089055806090 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 2 Jan 2010 13:43:50 +0530 Subject: Make sure not to spalt string arguments --- activerecord/lib/active_record/relation/query_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 432b33e174..4600aab574 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -116,7 +116,7 @@ module ActiveRecord args.first end - spawn(@relation.where(*conditions)) + conditions.is_a?(String) ? spawn(@relation.where(conditions)) : spawn(@relation.where(*conditions)) end private -- cgit v1.2.3 From f1acf1cc74bb9e847a4d27e97630d1b685764a93 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 2 Jan 2010 21:16:16 +0530 Subject: Give higher preference to second relation's equality predicates when merging --- activerecord/lib/active_record/relation.rb | 38 +++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 11 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 8756695d46..44e2061efa 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -18,16 +18,32 @@ module ActiveRecord def merge(r) raise ArgumentError, "Cannot merge a #{r.klass.name} relation with #{@klass.name} relation" if r.klass != @klass - joins(r.relation.joins(r.relation)). - group(r.send(:group_clauses).join(', ')). - order(r.send(:order_clauses).join(', ')). - where(r.send(:where_clause)). - limit(r.taken). - offset(r.skipped). - select(r.send(:select_clauses).join(', ')). - eager_load(r.eager_load_associations). - preload(r.preload_associations). - from(r.send(:sources).present? ? r.send(:from_clauses) : nil) + merged_relation = spawn(table) + + [self, r].each do |r| + merged_relation = merged_relation. + joins(r.relation.joins(r.relation)). + group(r.send(:group_clauses).join(', ')). + order(r.send(:order_clauses).join(', ')). + limit(r.taken). + offset(r.skipped). + select(r.send(:select_clauses).join(', ')). + eager_load(r.eager_load_associations). + preload(r.preload_associations). + from(r.send(:sources).present? ? r.send(:from_clauses) : nil) + end + + merged_wheres = @relation.wheres + + r.wheres.each do |w| + if w.is_a?(Arel::Predicates::Equality) + merged_wheres = merged_wheres.reject {|p| p.is_a?(Arel::Predicates::Equality) && p.operand1.name == w.operand1.name } + end + + merged_wheres << w + end + + merged_relation.where(*merged_wheres) end alias :& :merge @@ -129,7 +145,7 @@ module ActiveRecord end def spawn(relation = @relation) - relation = self.class.new(@klass, relation) + relation = Relation.new(@klass, relation) relation.readonly = @readonly relation.preload_associations = @preload_associations relation.eager_load_associations = @eager_load_associations -- cgit v1.2.3 From 32b48bf419eb22514be9a75981b9b0cf51a973a0 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 2 Jan 2010 22:43:40 +0530 Subject: Use arel predicates instead of strings wherever possible when merging relations --- activerecord/lib/active_record/relation.rb | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 44e2061efa..6eb1d907e6 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -18,19 +18,18 @@ module ActiveRecord def merge(r) raise ArgumentError, "Cannot merge a #{r.klass.name} relation with #{@klass.name} relation" if r.klass != @klass - merged_relation = spawn(table) + merged_relation = spawn(table).eager_load(r.eager_load_associations).preload(r.preload_associations) + merged_relation.readonly = r.readonly - [self, r].each do |r| + [self.relation, r.relation].each do |arel| merged_relation = merged_relation. - joins(r.relation.joins(r.relation)). - group(r.send(:group_clauses).join(', ')). - order(r.send(:order_clauses).join(', ')). - limit(r.taken). - offset(r.skipped). - select(r.send(:select_clauses).join(', ')). - eager_load(r.eager_load_associations). - preload(r.preload_associations). - from(r.send(:sources).present? ? r.send(:from_clauses) : nil) + joins(arel.joins(arel)). + group(arel.groupings). + order(arel.send(:order_clauses).join(', ')). + limit(arel.taken). + offset(arel.skipped). + select(arel.send(:select_clauses)). + from(arel.sources) end merged_wheres = @relation.wheres -- cgit v1.2.3 From 65200d2933bd337b8347dd32502a12c2fddf24f0 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 3 Jan 2010 00:08:59 +0530 Subject: Implement Relation#new --- activerecord/lib/active_record/relation.rb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 6eb1d907e6..0319781e03 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -15,6 +15,10 @@ module ActiveRecord @loaded, @readonly = false end + def new(*args, &block) + @klass.send(:with_scope, :create => create_scope) { @klass.new(*args, &block) } + end + def merge(r) raise ArgumentError, "Cannot merge a #{r.klass.name} relation with #{@klass.name} relation" if r.klass != @klass @@ -138,7 +142,7 @@ module ActiveRecord end def reset - @first = @last = nil + @first = @last = @create_scope = nil @records = [] self end @@ -181,6 +185,13 @@ module ActiveRecord end end + def create_scope + @create_scope ||= wheres.inject({}) do |hash, where| + hash[where.operand1.name] = where.operand2.value if where.is_a?(Arel::Predicates::Equality) + hash + end + end + def where_clause(join_string = " AND ") @relation.send(:where_clauses).join(join_string) end -- cgit v1.2.3 From ac1df91e5eac4959c0030e2b2ea39968f7f9ba1a Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 3 Jan 2010 00:16:14 +0530 Subject: Implement Relation#create and Relation#create! --- activerecord/lib/active_record/relation.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 0319781e03..b445ba67b7 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -16,7 +16,15 @@ module ActiveRecord end def new(*args, &block) - @klass.send(:with_scope, :create => create_scope) { @klass.new(*args, &block) } + with_create_scope { @klass.new(*args, &block) } + end + + def create(*args, &block) + with_create_scope { @klass.create(*args, &block) } + end + + def create!(*args, &block) + with_create_scope { @klass.create!(*args, &block) } end def merge(r) @@ -185,6 +193,10 @@ module ActiveRecord end end + def with_create_scope + @klass.send(:with_scope, :create => create_scope) { yield } + end + def create_scope @create_scope ||= wheres.inject({}) do |hash, where| hash[where.operand1.name] = where.operand2.value if where.is_a?(Arel::Predicates::Equality) -- cgit v1.2.3 From dcafe995bfe51e53dd04607956be9b54073e9cb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 31 Dec 2009 12:29:44 +0100 Subject: Make nested attributes behave like in 2.3.5 and add a sanity test for it with I18n. --- activerecord/lib/active_record/autosave_association.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 44c668b619..98ab64537e 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -267,7 +267,7 @@ module ActiveRecord unless valid = association.valid? if reflection.options[:autosave] association.errors.each do |attribute, message| - attribute = "#{reflection.name}_#{attribute}" + attribute = "#{reflection.name}.#{attribute}" errors[attribute] << message if errors[attribute].empty? end else -- cgit v1.2.3 From 7cc0a4cfa1d18c011d6e41f57d25eb10ed018eba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 31 Dec 2009 14:14:29 +0100 Subject: Use activerecord.errors.format as in Rails 2.3.5. --- activerecord/lib/active_record/locale/en.yml | 3 +++ 1 file changed, 3 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml index 092f5f0023..e33d389f8c 100644 --- a/activerecord/lib/active_record/locale/en.yml +++ b/activerecord/lib/active_record/locale/en.yml @@ -1,6 +1,9 @@ en: activerecord: errors: + # model.errors.full_messages format. + format: "{{attribute}} {{message}}" + # The values :model, :attribute and :value are always available for interpolation # The value :count is available when applicable. Can be used for pluralization. messages: -- cgit v1.2.3 From a9c790e10ffd06294200dbbed4692fbd60424800 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 3 Jan 2010 03:00:14 +0530 Subject: Simply methods for checking eager loaded tables references in the query --- activerecord/lib/active_record/associations.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 80e6acf34c..24ea8cef33 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1830,22 +1830,22 @@ module ActiveRecord end # Checks if the conditions reference a table other than the current model table - def include_eager_conditions?(options, tables = nil, joined_tables = nil) - ((tables || conditions_tables(options)) - (joined_tables || joined_tables(options))).any? + def include_eager_conditions?(options, joined_tables) + (conditions_tables(options) - joined_tables).any? end # Checks if the query order references a table other than the current model's table. - def include_eager_order?(options, tables = nil, joined_tables = nil) - ((tables || order_tables(options)) - (joined_tables || joined_tables(options))).any? + def include_eager_order?(options, joined_tables) + (order_tables(options) - joined_tables).any? end - def include_eager_select?(options, joined_tables = nil) - (selects_tables(options) - (joined_tables || joined_tables(options))).any? + def include_eager_select?(options, joined_tables) + (selects_tables(options) - joined_tables).any? end def references_eager_loaded_tables?(options) joined_tables = joined_tables(options) - include_eager_order?(options, nil, joined_tables) || include_eager_conditions?(options, nil, joined_tables) || include_eager_select?(options, joined_tables) + include_eager_order?(options, joined_tables) || include_eager_conditions?(options, joined_tables) || include_eager_select?(options, joined_tables) end def using_limitable_reflections?(reflections) -- cgit v1.2.3 From c51347152abc7d8a97fd8df3eecfdbfd07673b68 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 3 Jan 2010 03:08:01 +0530 Subject: Get rid of Model.construct_finder_arel_with_includes. Use construct_finder_arel instead --- .../associations/association_collection.rb | 2 +- activerecord/lib/active_record/base.rb | 21 ++++++++------------- activerecord/lib/active_record/named_scope.rb | 2 +- 3 files changed, 10 insertions(+), 15 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 1ceb0dbf96..cead614d0f 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -58,7 +58,7 @@ module ActiveRecord find_scope = construct_scope[:find].slice(:conditions, :order) with_scope(:find => find_scope) do - relation = @reflection.klass.send(:construct_finder_arel_with_includes, options) + relation = @reflection.klass.send(:construct_finder_arel, options) case args.first when :first, :last, :all diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 7cefba2b82..e77cc6e697 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -645,7 +645,7 @@ module ActiveRecord #:nodoc: options = args.extract_options! set_readonly_option!(options) - relation = construct_finder_arel_with_includes(options) + relation = construct_finder_arel(options) case args.first when :first, :last, :all @@ -1581,17 +1581,7 @@ module ActiveRecord #:nodoc: offset(construct_offset(options[:offset], scope)). from(options[:from]) - lock = (scope && scope[:lock]) || options[:lock] - relation = relation.lock if lock.present? - - relation = relation.readonly if options[:readonly] - - relation - end - - def construct_finder_arel_with_includes(options = {}) - relation = construct_finder_arel(options) - include_associations = merge_includes(scope(:find, :include), options[:include]) + include_associations = merge_includes(scope && scope[:include], options[:include]) if include_associations.any? if references_eager_loaded_tables?(options) @@ -1601,6 +1591,11 @@ module ActiveRecord #:nodoc: end end + lock = (scope && scope[:lock]) || options[:lock] + relation = relation.lock if lock.present? + + relation = relation.readonly if options[:readonly] + relation end @@ -1722,7 +1717,7 @@ module ActiveRecord #:nodoc: super unless all_attributes_exists?(attribute_names) if match.finder? options = arguments.extract_options! - relation = options.any? ? construct_finder_arel_with_includes(options) : scoped + relation = options.any? ? construct_finder_arel(options) : scoped relation.send :find_by_attributes, match, attribute_names, *arguments elsif match.instantiator? scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 5959265d5e..f63b249241 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -29,7 +29,7 @@ module ActiveRecord unless scoped?(:find) finder_needs_type_condition? ? active_relation.where(type_condition) : active_relation.spawn else - construct_finder_arel_with_includes + construct_finder_arel end end end -- cgit v1.2.3 From 6f5f23aaa7c4673fe923c6ad1450baaeccb40c02 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 3 Jan 2010 03:24:28 +0530 Subject: Add Relation#includes to be an equivalent of current finder option :include --- .../associations/association_collection.rb | 2 +- activerecord/lib/active_record/base.rb | 2 +- activerecord/lib/active_record/relation.rb | 18 +++++++++++++----- .../lib/active_record/relation/query_methods.rb | 4 ++++ 4 files changed, 19 insertions(+), 7 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index cead614d0f..358db6df1d 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -21,7 +21,7 @@ module ActiveRecord construct_sql end - delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :lock, :readonly, :having, :to => :scoped + delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped def select(select = nil, &block) if block_given? diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index e77cc6e697..b0bc7b928e 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -655,7 +655,7 @@ module ActiveRecord #:nodoc: end end - delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :lock, :readonly, :having, :to => :scoped + delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped # A convenience wrapper for find(:first, *args). You can pass in all the # same arguments to this method as you can to find(:first). diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index b445ba67b7..fc4d1a6960 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -5,13 +5,15 @@ module ActiveRecord delegate :to_sql, :to => :relation delegate :length, :collect, :map, :each, :all?, :to => :to_a - attr_reader :relation, :klass, :preload_associations, :eager_load_associations - attr_writer :readonly, :preload_associations, :eager_load_associations, :table + attr_reader :relation, :klass + attr_writer :readonly, :table + attr_accessor :preload_associations, :eager_load_associations, :include_associations def initialize(klass, relation) @klass, @relation = klass, relation @preload_associations = [] @eager_load_associations = [] + @include_associations = [] @loaded, @readonly = false end @@ -30,7 +32,7 @@ module ActiveRecord def merge(r) raise ArgumentError, "Cannot merge a #{r.klass.name} relation with #{@klass.name} relation" if r.klass != @klass - merged_relation = spawn(table).eager_load(r.eager_load_associations).preload(r.preload_associations) + merged_relation = spawn(table).eager_load(r.eager_load_associations).preload(r.preload_associations).includes(r.include_associations) merged_relation.readonly = r.readonly [self.relation, r.relation].each do |arel| @@ -74,7 +76,9 @@ module ActiveRecord def to_a return @records if loaded? - @records = if @eager_load_associations.any? + find_with_associations = @eager_load_associations.any? + + @records = if find_with_associations begin @klass.send(:find_with_associations, { :select => @relation.send(:select_clauses).join(', '), @@ -94,7 +98,10 @@ module ActiveRecord @klass.find_by_sql(@relation.to_sql) end - @preload_associations.each {|associations| @klass.send(:preload_associations, @records, associations) } + preload = @preload_associations + preload += @include_associations unless find_with_associations + preload.each {|associations| @klass.send(:preload_associations, @records, associations) } + @records.each { |record| record.readonly! } if @readonly @loaded = true @@ -160,6 +167,7 @@ module ActiveRecord relation.readonly = @readonly relation.preload_associations = @preload_associations relation.eager_load_associations = @eager_load_associations + relation.include_associations = @include_associations relation.table = table relation end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 4600aab574..cf2cc7ba70 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -5,6 +5,10 @@ module ActiveRecord spawn.tap {|r| r.preload_associations += Array.wrap(associations) } end + def includes(*associations) + spawn.tap {|r| r.include_associations += Array.wrap(associations) } + end + def eager_load(*associations) spawn.tap {|r| r.eager_load_associations += Array.wrap(associations) } end -- cgit v1.2.3 From eb7fdb94647c42e31370b7faa1a474a966053f4d Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 3 Jan 2010 03:27:14 +0530 Subject: Make Relation#includes behave exactly like the existing :include option --- activerecord/lib/active_record/base.rb | 13 ++---------- activerecord/lib/active_record/relation.rb | 33 +++++++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 14 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index b0bc7b928e..70776c7aa2 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1579,17 +1579,8 @@ module ActiveRecord #:nodoc: order(construct_order(options[:order], scope)). limit(construct_limit(options[:limit], scope)). offset(construct_offset(options[:offset], scope)). - from(options[:from]) - - include_associations = merge_includes(scope && scope[:include], options[:include]) - - if include_associations.any? - if references_eager_loaded_tables?(options) - relation = relation.eager_load(include_associations) - else - relation = relation.preload(include_associations) - end - end + from(options[:from]). + includes( merge_includes(scope && scope[:include], options[:include])) lock = (scope && scope[:lock]) || options[:lock] relation = relation.lock if lock.present? diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index fc4d1a6960..c828c0b751 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -76,7 +76,7 @@ module ActiveRecord def to_a return @records if loaded? - find_with_associations = @eager_load_associations.any? + find_with_associations = @eager_load_associations.any? || references_eager_loaded_tables? @records = if find_with_associations begin @@ -90,7 +90,7 @@ module ActiveRecord :offset => @relation.skipped, :from => (@relation.send(:from_clauses) if @relation.send(:sources).present?) }, - ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations, nil)) + ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations + @include_associations, nil)) rescue ThrowResult [] end @@ -157,7 +157,7 @@ module ActiveRecord end def reset - @first = @last = @create_scope = nil + @first = @last = @create_scope = @joined_tables = nil @records = [] self end @@ -216,5 +216,32 @@ module ActiveRecord @relation.send(:where_clauses).join(join_string) end + def references_eager_loaded_tables? + include_eager_order? || include_eager_conditions? || include_eager_select? + end + + def include_eager_order? + order_clause = @relation.send(:order_clauses).join(', ') + (tables_in_string(order_clause) - joined_tables).any? + end + + def include_eager_conditions? + (tables_in_string(where_clause) - joined_tables).any? + end + + def include_eager_select? + select_clause = @relation.send(:select_clauses).join(', ') + (tables_in_string(select_clause) - joined_tables).any? + end + + def joined_tables + @joined_tables ||= (tables_in_string(@relation.joins(relation)) + [table.name, table.table_alias]).compact.uniq + end + + def tables_in_string(string) + return [] if string.blank? + string.scan(/([a-zA-Z_][\.\w]+).?\./).flatten.uniq + end + end end -- cgit v1.2.3 From 0d1a2a3c2239ed02f39e3e4690b905b0e86e80be Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 3 Jan 2010 03:49:10 +0530 Subject: Remove unused code from association.rb now that Relation takes care of checking the referenced tables --- activerecord/lib/active_record/associations.rb | 78 -------------------------- 1 file changed, 78 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 24ea8cef33..a2cb41f9ab 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1770,84 +1770,6 @@ module ActiveRecord relation.to_sql end - def tables_in_string(string) - return [] if string.blank? - string.scan(/([a-zA-Z_][\.\w]+).?\./).flatten - end - - def tables_in_hash(hash) - return [] if hash.blank? - tables = hash.map do |key, value| - if value.is_a?(Hash) - key.to_s - else - tables_in_string(key) if key.is_a?(String) - end - end - tables.flatten.compact - end - - def conditions_tables(options) - # look in both sets of conditions - conditions = [scope(:find, :conditions), options[:conditions]].inject([]) do |all, cond| - case cond - when nil then all - when Array then all << tables_in_string(cond.first) - when Hash then all << tables_in_hash(cond) - else all << tables_in_string(cond) - end - end - conditions.flatten - end - - def order_tables(options) - order = [options[:order], scope(:find, :order) ].join(", ") - return [] unless order && order.is_a?(String) - tables_in_string(order) - end - - def selects_tables(options) - select = options[:select] - return [] unless select && select.is_a?(String) - tables_in_string(select) - end - - def joined_tables(options) - scope = scope(:find) - joins = options[:joins] - merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins]) - [table_name] + case merged_joins - when Symbol, Hash, Array - if array_of_strings?(merged_joins) - tables_in_string(merged_joins.join(' ')) - else - join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_joins, nil) - join_dependency.join_associations.collect {|join_association| [join_association.aliased_join_table_name, join_association.aliased_table_name]}.flatten.compact - end - else - tables_in_string(merged_joins) - end - end - - # Checks if the conditions reference a table other than the current model table - def include_eager_conditions?(options, joined_tables) - (conditions_tables(options) - joined_tables).any? - end - - # Checks if the query order references a table other than the current model's table. - def include_eager_order?(options, joined_tables) - (order_tables(options) - joined_tables).any? - end - - def include_eager_select?(options, joined_tables) - (selects_tables(options) - joined_tables).any? - end - - def references_eager_loaded_tables?(options) - joined_tables = joined_tables(options) - include_eager_order?(options, joined_tables) || include_eager_conditions?(options, joined_tables) || include_eager_select?(options, joined_tables) - end - def using_limitable_reflections?(reflections) reflections.reject { |r| [ :belongs_to, :has_one ].include?(r.macro) }.length.zero? end -- cgit v1.2.3 From e9ebf8b898e0dba4ca62cca7eb26987a9117a20e Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 3 Jan 2010 03:56:21 +0530 Subject: Cache Relation#to_sql --- activerecord/lib/active_record/relation.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index c828c0b751..31c7acf346 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -2,7 +2,6 @@ module ActiveRecord class Relation include QueryMethods, FinderMethods, CalculationMethods - delegate :to_sql, :to => :relation delegate :length, :collect, :map, :each, :all?, :to => :to_a attr_reader :relation, :klass @@ -157,7 +156,7 @@ module ActiveRecord end def reset - @first = @last = @create_scope = @joined_tables = nil + @first = @last = @create_scope = @joined_tables = @to_sql = nil @records = [] self end @@ -180,6 +179,10 @@ module ActiveRecord @primary_key ||= table[@klass.primary_key] end + def to_sql + @to_sql ||= @relation.to_sql + end + protected def method_missing(method, *args, &block) -- cgit v1.2.3 From 47da00e94b5b35fb98add859feae1bfc47d969ec Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 3 Jan 2010 03:58:34 +0530 Subject: Further simplify Relation#references_eager_loaded_tables? --- activerecord/lib/active_record/relation.rb | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 31c7acf346..114095b7ef 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -156,7 +156,7 @@ module ActiveRecord end def reset - @first = @last = @create_scope = @joined_tables = @to_sql = nil + @first = @last = @create_scope = @to_sql = nil @records = [] self end @@ -220,25 +220,8 @@ module ActiveRecord end def references_eager_loaded_tables? - include_eager_order? || include_eager_conditions? || include_eager_select? - end - - def include_eager_order? - order_clause = @relation.send(:order_clauses).join(', ') - (tables_in_string(order_clause) - joined_tables).any? - end - - def include_eager_conditions? - (tables_in_string(where_clause) - joined_tables).any? - end - - def include_eager_select? - select_clause = @relation.send(:select_clauses).join(', ') - (tables_in_string(select_clause) - joined_tables).any? - end - - def joined_tables - @joined_tables ||= (tables_in_string(@relation.joins(relation)) + [table.name, table.table_alias]).compact.uniq + joined_tables = (tables_in_string(@relation.joins(relation)) + [table.name, table.table_alias]).compact.uniq + (tables_in_string(to_sql) - joined_tables).any? end def tables_in_string(string) -- cgit v1.2.3 From 8e57deed8b4efad6ea1c551f415b74596111f890 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 3 Jan 2010 04:09:36 +0530 Subject: Remove optional join_dependency argument as Relation always supplies it --- activerecord/lib/active_record/associations.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index a2cb41f9ab..d74b21b690 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1465,8 +1465,7 @@ module ActiveRecord after_destroy(method_name) end - def find_with_associations(options = {}, join_dependency = nil) - join_dependency ||= JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins]) + def find_with_associations(options = {}, join_dependency) rows = select_all_rows(options, join_dependency) join_dependency.instantiate(rows) rescue ThrowResult -- cgit v1.2.3 From 8571aa613f1707401b0fe54c0fa1687604c48e0a Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sat, 2 Jan 2010 18:39:36 -0800 Subject: Revert "Remove optional join_dependency argument as Relation always supplies it" This reverts commit 8e57deed8b4efad6ea1c551f415b74596111f890. --- activerecord/lib/active_record/associations.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index d74b21b690..a2cb41f9ab 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1465,7 +1465,8 @@ module ActiveRecord after_destroy(method_name) end - def find_with_associations(options = {}, join_dependency) + def find_with_associations(options = {}, join_dependency = nil) + join_dependency ||= JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins]) rows = select_all_rows(options, join_dependency) join_dependency.instantiate(rows) rescue ThrowResult -- cgit v1.2.3 From 4939f95c9b0c1e68b442c94640831e05eff15a32 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 3 Jan 2010 11:08:57 +0530 Subject: Reapply "Remove optional join_dependency argument as Relation always supplies it" - Now without syntax errors --- activerecord/lib/active_record/associations.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index a2cb41f9ab..b2ae447d5e 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1465,8 +1465,7 @@ module ActiveRecord after_destroy(method_name) end - def find_with_associations(options = {}, join_dependency = nil) - join_dependency ||= JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins]) + def find_with_associations(options, join_dependency) rows = select_all_rows(options, join_dependency) join_dependency.instantiate(rows) rescue ThrowResult -- cgit v1.2.3 From 3eca0ab8388a84bf34a78395edf85e30c6943c63 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 3 Jan 2010 18:47:34 +0530 Subject: Give preference to the second relation's order when merging --- activerecord/lib/active_record/relation.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 114095b7ef..9cfd9b6d23 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -38,13 +38,16 @@ module ActiveRecord merged_relation = merged_relation. joins(arel.joins(arel)). group(arel.groupings). - order(arel.send(:order_clauses).join(', ')). limit(arel.taken). offset(arel.skipped). select(arel.send(:select_clauses)). from(arel.sources) end + relation_order = r.send(:order_clause) + merged_order = relation_order.present? ? relation_order : order_clause + merged_relation = merged_relation.order(merged_order) + merged_wheres = @relation.wheres r.wheres.each do |w| @@ -83,7 +86,7 @@ module ActiveRecord :select => @relation.send(:select_clauses).join(', '), :joins => @relation.joins(relation), :group => @relation.send(:group_clauses).join(', '), - :order => @relation.send(:order_clauses).join(', '), + :order => order_clause, :conditions => where_clause, :limit => @relation.taken, :offset => @relation.skipped, @@ -156,7 +159,7 @@ module ActiveRecord end def reset - @first = @last = @create_scope = @to_sql = nil + @first = @last = @create_scope = @to_sql = @order_clause = nil @records = [] self end @@ -219,6 +222,10 @@ module ActiveRecord @relation.send(:where_clauses).join(join_string) end + def order_clause + @order_clause ||= @relation.send(:order_clauses).join(', ') + end + def references_eager_loaded_tables? joined_tables = (tables_in_string(@relation.joins(relation)) + [table.name, table.table_alias]).compact.uniq (tables_in_string(to_sql) - joined_tables).any? -- cgit v1.2.3 From 8edfa8f82fecb23af506278d510ea900e6021e6b Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 3 Jan 2010 18:57:57 +0530 Subject: Move Relation#spawn and Relation#merge to a separate module --- activerecord/lib/active_record.rb | 1 + activerecord/lib/active_record/relation.rb | 47 +-------------------- .../lib/active_record/relation/spawn_methods.rb | 49 ++++++++++++++++++++++ 3 files changed, 51 insertions(+), 46 deletions(-) create mode 100644 activerecord/lib/active_record/relation/spawn_methods.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index cf439b0dc0..728dec8925 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -55,6 +55,7 @@ module ActiveRecord autoload :FinderMethods autoload :CalculationMethods autoload :PredicateBuilder + autoload :SpawnMethods end autoload :Base diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 9cfd9b6d23..a4d11f813d 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -1,6 +1,6 @@ module ActiveRecord class Relation - include QueryMethods, FinderMethods, CalculationMethods + include QueryMethods, FinderMethods, CalculationMethods, SpawnMethods delegate :length, :collect, :map, :each, :all?, :to => :to_a @@ -28,41 +28,6 @@ module ActiveRecord with_create_scope { @klass.create!(*args, &block) } end - def merge(r) - raise ArgumentError, "Cannot merge a #{r.klass.name} relation with #{@klass.name} relation" if r.klass != @klass - - merged_relation = spawn(table).eager_load(r.eager_load_associations).preload(r.preload_associations).includes(r.include_associations) - merged_relation.readonly = r.readonly - - [self.relation, r.relation].each do |arel| - merged_relation = merged_relation. - joins(arel.joins(arel)). - group(arel.groupings). - limit(arel.taken). - offset(arel.skipped). - select(arel.send(:select_clauses)). - from(arel.sources) - end - - relation_order = r.send(:order_clause) - merged_order = relation_order.present? ? relation_order : order_clause - merged_relation = merged_relation.order(merged_order) - - merged_wheres = @relation.wheres - - r.wheres.each do |w| - if w.is_a?(Arel::Predicates::Equality) - merged_wheres = merged_wheres.reject {|p| p.is_a?(Arel::Predicates::Equality) && p.operand1.name == w.operand1.name } - end - - merged_wheres << w - end - - merged_relation.where(*merged_wheres) - end - - alias :& :merge - def respond_to?(method, include_private = false) return true if @relation.respond_to?(method, include_private) || Array.method_defined?(method) @@ -164,16 +129,6 @@ module ActiveRecord self end - def spawn(relation = @relation) - relation = Relation.new(@klass, relation) - relation.readonly = @readonly - relation.preload_associations = @preload_associations - relation.eager_load_associations = @eager_load_associations - relation.include_associations = @include_associations - relation.table = table - relation - end - def table @table ||= Arel::Table.new(@klass.table_name, Arel::Sql::Engine.new(@klass)) end diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb new file mode 100644 index 0000000000..4a64f0c6a9 --- /dev/null +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -0,0 +1,49 @@ +module ActiveRecord + module SpawnMethods + def spawn(relation = @relation) + relation = Relation.new(@klass, relation) + relation.readonly = @readonly + relation.preload_associations = @preload_associations + relation.eager_load_associations = @eager_load_associations + relation.include_associations = @include_associations + relation.table = table + relation + end + + def merge(r) + raise ArgumentError, "Cannot merge a #{r.klass.name} relation with #{@klass.name} relation" if r.klass != @klass + + merged_relation = spawn(table).eager_load(r.eager_load_associations).preload(r.preload_associations).includes(r.include_associations) + merged_relation.readonly = r.readonly + + [self.relation, r.relation].each do |arel| + merged_relation = merged_relation. + joins(arel.joins(arel)). + group(arel.groupings). + limit(arel.taken). + offset(arel.skipped). + select(arel.send(:select_clauses)). + from(arel.sources) + end + + relation_order = r.send(:order_clause) + merged_order = relation_order.present? ? relation_order : order_clause + merged_relation = merged_relation.order(merged_order) + + merged_wheres = @relation.wheres + + r.wheres.each do |w| + if w.is_a?(Arel::Predicates::Equality) + merged_wheres = merged_wheres.reject {|p| p.is_a?(Arel::Predicates::Equality) && p.operand1.name == w.operand1.name } + end + + merged_wheres << w + end + + merged_relation.where(*merged_wheres) + end + + alias :& :merge + + end +end -- cgit v1.2.3 From 22bfd8b09804db28e05e598e062d58fd2ab36485 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 3 Jan 2010 19:20:40 +0530 Subject: Rename a variable name for consistency --- activerecord/lib/active_record/relation.rb | 8 ++++---- activerecord/lib/active_record/relation/query_methods.rb | 2 +- activerecord/lib/active_record/relation/spawn_methods.rb | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index a4d11f813d..ec8ae21726 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -6,13 +6,13 @@ module ActiveRecord attr_reader :relation, :klass attr_writer :readonly, :table - attr_accessor :preload_associations, :eager_load_associations, :include_associations + attr_accessor :preload_associations, :eager_load_associations, :includes_associations def initialize(klass, relation) @klass, @relation = klass, relation @preload_associations = [] @eager_load_associations = [] - @include_associations = [] + @includes_associations = [] @loaded, @readonly = false end @@ -57,7 +57,7 @@ module ActiveRecord :offset => @relation.skipped, :from => (@relation.send(:from_clauses) if @relation.send(:sources).present?) }, - ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations + @include_associations, nil)) + ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations + @includes_associations, nil)) rescue ThrowResult [] end @@ -66,7 +66,7 @@ module ActiveRecord end preload = @preload_associations - preload += @include_associations unless find_with_associations + preload += @includes_associations unless find_with_associations preload.each {|associations| @klass.send(:preload_associations, @records, associations) } @records.each { |record| record.readonly! } if @readonly diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index cf2cc7ba70..525a9cb365 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -6,7 +6,7 @@ module ActiveRecord end def includes(*associations) - spawn.tap {|r| r.include_associations += Array.wrap(associations) } + spawn.tap {|r| r.includes_associations += Array.wrap(associations) } end def eager_load(*associations) diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 4a64f0c6a9..7b15279253 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -5,7 +5,7 @@ module ActiveRecord relation.readonly = @readonly relation.preload_associations = @preload_associations relation.eager_load_associations = @eager_load_associations - relation.include_associations = @include_associations + relation.includes_associations = @includes_associations relation.table = table relation end @@ -13,7 +13,7 @@ module ActiveRecord def merge(r) raise ArgumentError, "Cannot merge a #{r.klass.name} relation with #{@klass.name} relation" if r.klass != @klass - merged_relation = spawn(table).eager_load(r.eager_load_associations).preload(r.preload_associations).includes(r.include_associations) + merged_relation = spawn(table).eager_load(r.eager_load_associations).preload(r.preload_associations).includes(r.includes_associations) merged_relation.readonly = r.readonly [self.relation, r.relation].each do |arel| -- cgit v1.2.3 From af5e1b4cc6fd3ae5f5e175751373a5e60934385b Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 3 Jan 2010 19:42:29 +0530 Subject: Add Relation#except --- .../lib/active_record/relation/spawn_methods.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 7b15279253..b9e3457aac 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -45,5 +45,27 @@ module ActiveRecord alias :& :merge + def except(*skips) + result = Relation.new(@klass, table) + result.table = table + + [:eager_load, :preload, :includes].each do |load_method| + result = result.send(load_method, send(:"#{load_method}_associations")) + end + + result.readonly = self.readonly unless skips.include?(:readonly) + + result = result.joins(@relation.joins(@relation)) unless skips.include?(:joins) + result = result.group(@relation.groupings) unless skips.include?(:group) + result = result.limit(@relation.taken) unless skips.include?(:limit) + result = result.offset(@relation.skipped) unless skips.include?(:offset) + result = result.select(@relation.send(:select_clauses)) unless skips.include?(:select) + result = result.from(@relation.sources) unless skips.include?(:from) + result = result.order(order_clause) unless skips.include?(:order) + result = result.where(*@relation.wheres) unless skips.include?(:where) + + result + end + end end -- cgit v1.2.3 From 3db876cb761837ebf9b02d22846353e277ff14cd Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 3 Jan 2010 20:50:04 +0530 Subject: Relation#merge and Relation#except should respect havings --- activerecord/lib/active_record/relation/spawn_methods.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index b9e3457aac..ea58812faa 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -23,7 +23,8 @@ module ActiveRecord limit(arel.taken). offset(arel.skipped). select(arel.send(:select_clauses)). - from(arel.sources) + from(arel.sources). + having(arel.havings) end relation_order = r.send(:order_clause) @@ -63,6 +64,7 @@ module ActiveRecord result = result.from(@relation.sources) unless skips.include?(:from) result = result.order(order_clause) unless skips.include?(:order) result = result.where(*@relation.wheres) unless skips.include?(:where) + result = result.having(*@relation.havings) unless skips.include?(:having) result end -- cgit v1.2.3 From 00f3f6dc3145f2d7e8b8dadd966008cd0fa54636 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 3 Jan 2010 21:24:09 +0530 Subject: Relation#merge and Relation#except should respect locks --- activerecord/lib/active_record/relation/spawn_methods.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index ea58812faa..a637e97155 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -24,7 +24,8 @@ module ActiveRecord offset(arel.skipped). select(arel.send(:select_clauses)). from(arel.sources). - having(arel.havings) + having(arel.havings). + lock(arel.locked) end relation_order = r.send(:order_clause) @@ -65,6 +66,7 @@ module ActiveRecord result = result.order(order_clause) unless skips.include?(:order) result = result.where(*@relation.wheres) unless skips.include?(:where) result = result.having(*@relation.havings) unless skips.include?(:having) + result = result.lock(@relation.locked) unless skips.include?(:lock) result end -- cgit v1.2.3 From a115b5d79a850bb56cd3c9db9a05d6da35e3d7be Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 4 Jan 2010 02:05:18 +0530 Subject: Ensure using proper engine for Arel::Table --- activerecord/lib/active_record/base.rb | 10 ++++++++-- activerecord/lib/active_record/relation.rb | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 70776c7aa2..ec7725d256 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1510,11 +1510,17 @@ module ActiveRecord #:nodoc: end def active_relation_table(table_name_alias = nil) - Arel::Table.new(table_name, :as => table_name_alias) + Arel::Table.new(table_name, :as => table_name_alias, :engine => active_relation_engine) end def active_relation_engine - @active_relation_engine ||= Arel::Sql::Engine.new(self) + @active_relation_engine ||= begin + if self == ActiveRecord::Base + Arel::Table.engine + else + connection_handler.connection_pools[name] ? Arel::Sql::Engine.new(self) : superclass.active_relation_engine + end + end end private diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index ec8ae21726..6b9925d4e7 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -130,7 +130,7 @@ module ActiveRecord end def table - @table ||= Arel::Table.new(@klass.table_name, Arel::Sql::Engine.new(@klass)) + @table ||= Arel::Table.new(@klass.table_name, :engine => @klass.active_relation_engine) end def primary_key -- cgit v1.2.3