From 080974784582e1e289c2948227b446bc56d404a1 Mon Sep 17 00:00:00 2001 From: Nik Wakelin Date: Sat, 2 Aug 2008 17:44:02 +1200 Subject: Added MigrationProxy to defer loading of Migration classes until they are actually required by the migrator Signed-off-by: Michael Koziarski [#747 state:resolved] --- activerecord/lib/active_record/migration.rb | 32 +++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 731a350854..fd77f27b77 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -349,6 +349,27 @@ module ActiveRecord end end + # MigrationProxy is used to defer loading of the actual migration classes + # until they are needed + class MigrationProxy + + attr_accessor :name, :version, :filename + + delegate :migrate, :announce, :write, :to=>:migration + + private + + def migration + @migration ||= load_migration + end + + def load_migration + load(filename) + name.constantize + end + + end + class Migrator#:nodoc: class << self def migrate(migrations_path, target_version = nil) @@ -437,7 +458,7 @@ module ActiveRecord runnable.pop if down? && !target.nil? runnable.each do |migration| - Base.logger.info "Migrating to #{migration} (#{migration.version})" + Base.logger.info "Migrating to #{migration.name} (#{migration.version})" # On our way up, we skip migrating the ones we've already migrated # On our way down, we skip reverting the ones we've never migrated @@ -470,11 +491,10 @@ module ActiveRecord raise DuplicateMigrationNameError.new(name.camelize) end - load(file) - - klasses << returning(name.camelize.constantize) do |klass| - class << klass; attr_accessor :version end - klass.version = version + klasses << returning(MigrationProxy.new) do |migration| + migration.name = name.camelize + migration.version = version + migration.filename = file end end -- cgit v1.2.3 From 7f6e7ba1f7e8735f1c3f30ba125b5432f00d2a70 Mon Sep 17 00:00:00 2001 From: Ernie Miller Date: Sat, 2 Aug 2008 11:47:05 -0400 Subject: Fixed AssociationCollection#<< resulting in unexpected values in @target when :uniq => true Signed-off-by: Michael Koziarski --- activerecord/lib/active_record/associations/association_collection.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (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 a28be9eed1..9061037b39 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -344,7 +344,7 @@ module ActiveRecord callback(:before_add, record) yield(record) if block_given? @target ||= [] unless loaded? - @target << record + @target << record unless @reflection.options[:uniq] && @target.include?(record) callback(:after_add, record) record end -- cgit v1.2.3 From c7375d74d9fff3219d4ea389ba9e36a90afe9d33 Mon Sep 17 00:00:00 2001 From: Michalis Polakis Date: Mon, 11 Aug 2008 14:53:24 +0100 Subject: Alias subquery used in calculations, to provide better compatibility with databases such as MonetDB Signed-off-by: Michael Koziarski Signed-off-by: Tom Ward [#796 state:committed] --- activerecord/lib/active_record/calculations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index 2ca1a0aaa3..246f87b7a9 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -211,7 +211,7 @@ module ActiveRecord sql << " ORDER BY #{options[:order]} " if options[:order] add_limit!(sql, options, scope) - sql << ')' if use_workaround + sql << ') AS #{aggregate_alias}_subquery' if use_workaround sql end -- cgit v1.2.3 From 81c12d1f6359eb5e52b376f1f3552097a144cc8b Mon Sep 17 00:00:00 2001 From: Trevor Turk Date: Mon, 11 Aug 2008 21:17:14 -0500 Subject: move logging of protected attribute removal into log_protected_attribute_removal method Signed-off-by: Michael Koziarski [#804 status:committed] --- activerecord/lib/active_record/base.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 29c2995334..e5b6e3a02f 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2594,7 +2594,7 @@ module ActiveRecord #:nodoc: removed_attributes = attributes.keys - safe_attributes.keys if removed_attributes.any? - logger.debug "WARNING: Can't mass-assign these protected attributes: #{removed_attributes.join(', ')}" + log_protected_attribute_removal(removed_attributes) end safe_attributes @@ -2609,6 +2609,10 @@ module ActiveRecord #:nodoc: end end + def log_protected_attribute_removal(*attributes) + logger.debug "WARNING: Can't mass-assign these protected attributes: #{attributes.join(', ')}" + end + # The primary key and inheritance column can never be set by mass-assignment for security reasons. def attributes_protected_by_default default = [ self.class.primary_key, self.class.inheritance_column ] -- cgit v1.2.3 From 992fda16ed662f028700d63a8dcbd1837f1d58ab Mon Sep 17 00:00:00 2001 From: Tom Lea Date: Mon, 11 Aug 2008 18:16:58 +0100 Subject: Serialized attributes will now always be saved even with partial_updates turned on. Signed-off-by: Michael Koziarski [#788 state:committed] --- activerecord/lib/active_record/dirty.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/dirty.rb b/activerecord/lib/active_record/dirty.rb index 4ce0356457..63bf8c8f5b 100644 --- a/activerecord/lib/active_record/dirty.rb +++ b/activerecord/lib/active_record/dirty.rb @@ -134,7 +134,9 @@ module ActiveRecord def update_with_dirty if partial_updates? - update_without_dirty(changed) + # Serialized attributes should always be written in case they've been + # changed in place. + update_without_dirty(changed | self.class.serialized_attributes.keys) else update_without_dirty end -- cgit v1.2.3 From 8c300ebde2826de09d1b6af2df482276a771560d Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 12 Aug 2008 20:39:41 +0200 Subject: activerecord/lib/active_record/base.rb --- activerecord/lib/active_record/base.rb | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 1838287616..5b70529bf9 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -752,13 +752,15 @@ module ActiveRecord #:nodoc: end # Updates all records with details given if they match a set of conditions supplied, limits and order can - # also be supplied. + # also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the + # database. It does not instantiate the involved models and it does not trigger Active Record callbacks. # # ==== Attributes # - # * +updates+ - A String of column and value pairs that will be set on any records that match conditions. + # * +updates+ - An string of column and value pairs that will be set on any records that match conditions. + # What goes into the SET clause. # * +conditions+ - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro for more info. - # * +options+ - Additional options are :limit and/or :order, see the examples for usage. + # * +options+ - Additional options are :limit and :order, see the examples for usage. # # ==== Examples # @@ -780,8 +782,8 @@ module ActiveRecord #:nodoc: connection.update(sql, "#{name} Update") end - # Destroys the records matching +conditions+ by instantiating each record and calling the destroy method. - # This means at least 2*N database queries to destroy N records, so avoid destroy_all if you are deleting + # Destroys the records matching +conditions+ by instantiating each record and calling their +destroy+ method. + # This means at least 2*N database queries to destroy N records, so avoid +destroy_all+ if you are deleting # many records. If you want to simply delete records without worrying about dependent associations or # callbacks, use the much faster +delete_all+ method instead. # @@ -800,8 +802,9 @@ module ActiveRecord #:nodoc: end # Deletes the records matching +conditions+ without instantiating the records first, and hence not - # calling the destroy method and invoking callbacks. This is a single SQL query, much more efficient - # than destroy_all. + # calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that + # goes straight to the database, much more efficient than +destroy_all+. Careful with relations + # though, in particular :dependent is not taken into account. # # ==== Attributes # @@ -811,8 +814,8 @@ module ActiveRecord #:nodoc: # # Post.delete_all "person_id = 5 AND (category = 'Something' OR category = 'Else')" # - # This deletes the affected posts all at once with a single DELETE query. If you need to destroy dependent - # associations or call your before_ or after_destroy callbacks, use the +destroy_all+ method instead. + # This deletes 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) sql = "DELETE FROM #{quoted_table_name} " add_conditions!(sql, conditions, scope(:find)) -- cgit v1.2.3 From 99352fc49ae28334de2e56ad07286f99dbd2bca5 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 12 Aug 2008 20:47:48 +0200 Subject: An string -> A string --- 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 5b70529bf9..d73d424339 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -757,7 +757,7 @@ module ActiveRecord #:nodoc: # # ==== Attributes # - # * +updates+ - An string of column and value pairs that will be set on any records that match conditions. + # * +updates+ - A string of column and value pairs that will be set on any records that match conditions. # What goes into the SET clause. # * +conditions+ - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro for more info. # * +options+ - Additional options are :limit and :order, see the examples for usage. -- cgit v1.2.3 From d7514c6dafb331901cfcb9c71152fab0e4261d67 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 12 Aug 2008 21:09:54 +0200 Subject: added a few docs about transactions --- activerecord/lib/active_record/callbacks.rb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index be2621fdb6..0f2455fbfc 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -3,7 +3,7 @@ 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 - # dependent objects are deleted when destroy is called (by overwriting +before_destroy+) or to massage attributes + # dependent objects are deleted when +destroy+ is called (by overwriting +before_destroy+) or to massage attributes # before they're validated (by overwriting +before_validation+). As an example of the callbacks initiated, consider # the Base#save call: # @@ -169,6 +169,11 @@ module ActiveRecord # If a before_* callback returns +false+, all the later callbacks and the associated action are cancelled. If an after_* callback returns # +false+, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks # defined as methods on the model, which are called last. + # + # == Transactions + # + # The entire callback chain for +save+ and +destroy+ runs within their transaction. + # If the action is cancelled a rollback is performed. module Callbacks CALLBACKS = %w( after_find after_initialize before_save after_save before_create after_create before_update after_update before_validation @@ -197,6 +202,8 @@ module ActiveRecord def before_save() end # Is called _after_ Base.save (regardless of whether it's a +create+ or +update+ save). + # Note that this callback is still wrapped in the transaction around +save+. For example, if you + # invoke an external indexer at this point it won't see the changes in the database. # # class Contact < ActiveRecord::Base # after_save { logger.info( 'New contact saved!' ) } @@ -214,6 +221,8 @@ module ActiveRecord def before_create() end # Is called _after_ Base.save on new objects that haven't been saved yet (no record exists). + # Note that this callback is still wrapped in the transaction around +save+. For example, if you + # invoke an external indexer at this point it won't see the changes in the database. def after_create() end def create_with_callbacks #:nodoc: return false if callback(:before_create) == false @@ -227,6 +236,8 @@ module ActiveRecord def before_update() end # Is called _after_ Base.save on existing objects that have a record. + # Note that this callback is still wrapped in the transaction around +save+. For example, if you + # invoke an external indexer at this point it won't see the changes in the database. def after_update() end def update_with_callbacks(*args) #:nodoc: -- cgit v1.2.3 From 3b8b7a78df62db1f55c27b2b1a612a605cd9e56f Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 12 Aug 2008 21:52:38 +0200 Subject: removed a wrong statement, I'll look into this to document it better --- activerecord/lib/active_record/callbacks.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 0f2455fbfc..9f3b0765ca 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -173,7 +173,6 @@ module ActiveRecord # == Transactions # # The entire callback chain for +save+ and +destroy+ runs within their transaction. - # If the action is cancelled a rollback is performed. module Callbacks CALLBACKS = %w( after_find after_initialize before_save after_save before_create after_create before_update after_update before_validation -- cgit v1.2.3 From a5aad2e81febfa1a8d9fea0faffb5a3b4535982b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarmo=20T=C3=A4nav?= Date: Wed, 13 Aug 2008 06:18:01 +0300 Subject: Fixed Time/Date object serialization Time/Date objects used to be converted to_s instead of to_uaml which made them unserializable. --- activerecord/lib/active_record/base.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index e5b6e3a02f..2c4ead081d 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2626,8 +2626,15 @@ module ActiveRecord #:nodoc: quoted = {} connection = self.class.connection attribute_names.each do |name| - if column = column_for_attribute(name) - quoted[name] = connection.quote(read_attribute(name), column) unless !include_primary_key && column.primary + if (column = column_for_attribute(name)) && (include_primary_key || !column.primary) + value = read_attribute(name) + + # We need explicit to_yaml because quote() does not properly convert Time/Date fields to YAML. + if value && self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time)) + value = value.to_yaml + end + + quoted[name] = connection.quote(value, column) end end include_readonly_attributes ? quoted : remove_readonly_attributes(quoted) -- cgit v1.2.3 From 1ee9e3fa5c924bef4aba3d53796f48f5badbd06f Mon Sep 17 00:00:00 2001 From: Eloy Duran Date: Wed, 13 Aug 2008 13:36:39 +0200 Subject: Fix ActiveRecord::NamedScope::Scope#respond_to? [#818 state:resolved] Signed-off-by: Pratik Naik --- activerecord/lib/active_record/named_scope.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index d5a1c5fe08..0902018155 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -103,7 +103,7 @@ module ActiveRecord attr_reader :proxy_scope, :proxy_options [].methods.each do |m| - unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|find|count|sum|average|maximum|minimum|paginate|first|last|empty?|any?)/ + unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|find|count|sum|average|maximum|minimum|paginate|first|last|empty?|any?|respond_to?)/ delegate m, :to => :proxy_found end end @@ -140,6 +140,10 @@ module ActiveRecord @found ? @found.empty? : count.zero? end + def respond_to?(method) + super || @proxy_scope.respond_to?(method) + end + def any? if block_given? proxy_found.any? { |*block_args| yield(*block_args) } -- cgit v1.2.3 From 37054e6eb57a1ebe330f31323d25355ff3069cb5 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Thu, 14 Aug 2008 01:21:58 +0200 Subject: documented how to trigger rollbacks within the callback chain, and related gotchas --- activerecord/lib/active_record/callbacks.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 9f3b0765ca..f1983b6496 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -172,7 +172,16 @@ module ActiveRecord # # == Transactions # - # The entire callback chain for +save+ and +destroy+ runs within their transaction. + # The entire callback chain for +save+ and +destroy+ runs within their transaction, including + # the after_* hooks. Cancellation does not trigger a rollback. To rollback + # the transaction just raise an exception the same way you do for regular transactions. + # + # Note though that such an exception bypasses the regular call chain in Active Record: + # If ActiveRecord::Rollback is raised both +save+ and +destroy+ return +nil+. On the other + # hand save! does *not* raise ActiveRecord::RecordNotSaved, and does not raise + # anything else for that matter, save! just returns +nil+ in that case. + # If any other exception is raised it goes up until it reaches the caller, no matter + # which one of the three actions was being performed. module Callbacks CALLBACKS = %w( after_find after_initialize before_save after_save before_create after_create before_update after_update before_validation -- cgit v1.2.3 From 73ef94e9675ef6db85f18f1e3c70bf6ddfc1260a Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Thu, 14 Aug 2008 01:44:42 +0200 Subject: in save! docs: added a note about what happens if AR::Rollback is raised somewhere in the callback chain --- activerecord/lib/active_record/base.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index d73d424339..cf7ae97452 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2249,8 +2249,10 @@ module ActiveRecord #:nodoc: create_or_update end - # Attempts to save the record, but instead of just returning false if it couldn't happen, it raises a - # RecordNotSaved exception + # Attempts to save the record, but instead of just returning false if it couldn't happen, it raises an + # ActiveRecord::RecordNotSaved exception. However, if the callback chain raises ActiveRecord::Rollback + # to rollback the transaction that wraps save! no exception is raised, save! just + # returns +nil+. See ActiveRecord::Callbacks for further details. def save! create_or_update || raise(RecordNotSaved) end -- cgit v1.2.3 From ce40df20ec8541b6ccd1c7053b504d6345396b2f Mon Sep 17 00:00:00 2001 From: Sunny Date: Sun, 17 Aug 2008 19:21:13 +0200 Subject: Wrong default for validate_uniqueness_of's :case_sensitive --- activerecord/lib/active_record/validations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index e7a9676394..b8b695e529 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -597,7 +597,7 @@ module ActiveRecord # Configuration options: # * :message - Specifies a custom error message (default is: "has already been taken"). # * :scope - One or more columns by which to limit the scope of the uniqueness constraint. - # * :case_sensitive - Looks for an exact match. Ignored by non-text columns (+false+ by default). + # * :case_sensitive - Looks for an exact match. Ignored by non-text columns (+true+ by default). # * :allow_nil - If set to true, skips this validation if the attribute is +nil+ (default is +false+). # * :allow_blank - If set to true, skips this validation if the attribute is blank (default is +false+). # * :if - Specifies a method, proc or string to call to determine if the validation should -- cgit v1.2.3 From f392a639fc6e71dd0541b192b3c89a65d30bd818 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sun, 17 Aug 2008 23:34:22 +0200 Subject: complete revision of the docs of association_proxy.rb --- .../associations/association_proxy.rb | 55 ++++++++++++++++++++-- 1 file changed, 50 insertions(+), 5 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 77fc827e11..8a3186ded2 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -57,92 +57,127 @@ module ActiveRecord reset end + # Returns the owner of the proxy. def proxy_owner @owner end + # Returns the reflection object that represents the association handled + # by the proxy. def proxy_reflection @reflection end + # Returns the target of the proxy, same as +target+. def proxy_target @target end + # Does the proxy or its target respond to +symbol+? def respond_to?(symbol, include_priv = false) proxy_respond_to?(symbol, include_priv) || (load_target && @target.respond_to?(symbol, include_priv)) end - # Explicitly proxy === because the instance method removal above - # doesn't catch it. + # Forwards === explicitly to the target because the instance method + # removal above doesn't catch it. Loads the target if needed. def ===(other) load_target other === @target end + # Returns the name of the table of the related class: + # + # post.comments.aliased_table_name # => "comments" + # def aliased_table_name @reflection.klass.table_name end + # Returns the SQL string that corresponds to the :conditions + # option of the macro, if given, or +nil+ otherwise. def conditions @conditions ||= interpolate_sql(@reflection.sanitized_conditions) if @reflection.sanitized_conditions end alias :sql_conditions :conditions + # Resets the loaded flag to +false+ and sets the target to +nil+. def reset @loaded = false @target = nil end + # Reloads the target and returns +self+ on success. def reload reset load_target self unless @target.nil? end + # Has the target been already loaded? def loaded? @loaded end + # Asserts the target has been loaded setting the loaded flag to +true+. def loaded @loaded = true end + # Returns the target of this proxy, same as +proxy_target+. def target @target end + # Sets the target of this proxy to +target+, and the loaded flag to +true+. def target=(target) @target = target loaded end + # Forwards the call to the target. Loads the target if needed. def inspect load_target @target.inspect end protected + # Does the association have a :dependent option? def dependent? @reflection.options[:dependent] end + # Returns a string with the IDs of +records+ joined with a comma, quoted + # if needed. The result is ready to be inserted into a SQL IN clause. + # + # quoted_record_ids(records) # => "23,56,58,67" + # def quoted_record_ids(records) records.map { |record| record.quoted_id }.join(',') end + # Interpolates the SQL in options[key] and assigns the result + # back, for any +key+ in +keys+ that's present in +options+. + # + # Meant to be used like this: + # + # interpolate_sql_options!(@reflection.options, :finder_sql) + # def interpolate_sql_options!(options, *keys) keys.each { |key| options[key] &&= interpolate_sql(options[key]) } end + # Forwards the call to the owner. def interpolate_sql(sql, record = nil) @owner.send(:interpolate_sql, sql, record) end + # Forwards the call to the reflection class. def sanitize_sql(sql) @reflection.klass.send(:sanitize_sql, sql) end + # Assigns the ID of the owner to the corresponding foreign key in +record+. + # If the association is polymorphic the type of the owner is also set. def set_belongs_to_association_for(record) if @reflection.options[:as] record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record? @@ -152,6 +187,7 @@ module ActiveRecord end end + # Merges into +options+ the ones coming from the reflection. def merge_options_from_reflection!(options) options.reverse_merge!( :group => @reflection.options[:group], @@ -164,11 +200,13 @@ module ActiveRecord ) end + # Forwards +with_scope+ to the reflection. def with_scope(*args, &block) @reflection.klass.send :with_scope, *args, &block end private + # Forwards any missing method call to the target. def method_missing(method, *args) if load_target if block_given? @@ -202,12 +240,17 @@ module ActiveRecord reset end - # Can be overwritten by associations that might have the foreign key available for an association without - # having the object itself (and still being a new record). Currently, only belongs_to presents this scenario. + # Can be overwritten by associations that might have the foreign key + # available for an association without having the object itself (and + # still being a new record). Currently, only +belongs_to+ presents + # this scenario (both vanilla and polymorphic). def foreign_key_present false end + # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of + # the kind of the class of the associated objects. Meant to be used as + # a sanity check when you are about to assing an associated record. def raise_on_type_mismatch(record) unless record.is_a?(@reflection.klass) message = "#{@reflection.class_name}(##{@reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})" @@ -215,11 +258,13 @@ module ActiveRecord end end - # Array#flatten has problems with recursive arrays. Going one level deeper solves the majority of the problems. + # Array#flatten has problems with recursive arrays. Going one level + # deeper solves the majority of the problems. def flatten_deeper(array) array.collect { |element| element.respond_to?(:flatten) ? element.flatten : element }.flatten end + # Returns the ID of the owner, quoted if needed. def owner_quoted_id @owner.quoted_id end -- cgit v1.2.3 From cd2e535a8bd6f32002550e1e6bc25957e8bbd872 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sun, 17 Aug 2008 23:36:43 +0200 Subject: typo --- 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 8a3186ded2..cf09ab7043 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -250,7 +250,7 @@ module ActiveRecord # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of # the kind of the class of the associated objects. Meant to be used as - # a sanity check when you are about to assing an associated record. + # a sanity check when you are about to assign an associated record. def raise_on_type_mismatch(record) unless record.is_a?(@reflection.klass) message = "#{@reflection.class_name}(##{@reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})" -- cgit v1.2.3 From 48f56bf2beace63a20b0def4176274804e088c55 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Mon, 18 Aug 2008 02:14:53 +0200 Subject: escaped some otherwise autolinked words --- .../associations/association_proxy.rb | 32 +++++++++++----------- 1 file changed, 16 insertions(+), 16 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 cf09ab7043..78b4c137a7 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -39,7 +39,7 @@ module ActiveRecord # though the object behind blog.posts is not an Array, but an # ActiveRecord::Associations::HasManyAssociation. # - # The @target object is not loaded until needed. For example, + # The @target object is not \loaded until needed. For example, # # blog.posts.count # @@ -68,18 +68,18 @@ module ActiveRecord @reflection end - # Returns the target of the proxy, same as +target+. + # Returns the \target of the proxy, same as +target+. def proxy_target @target end - # Does the proxy or its target respond to +symbol+? + # Does the proxy or its \target respond to +symbol+? def respond_to?(symbol, include_priv = false) proxy_respond_to?(symbol, include_priv) || (load_target && @target.respond_to?(symbol, include_priv)) end - # Forwards === explicitly to the target because the instance method - # removal above doesn't catch it. Loads the target if needed. + # Forwards === explicitly to the \target because the instance method + # removal above doesn't catch it. Loads the \target if needed. def ===(other) load_target other === @target @@ -100,25 +100,25 @@ module ActiveRecord end alias :sql_conditions :conditions - # Resets the loaded flag to +false+ and sets the target to +nil+. + # Resets the \loaded flag to +false+ and sets the \target to +nil+. def reset @loaded = false @target = nil end - # Reloads the target and returns +self+ on success. + # Reloads the \target and returns +self+ on success. def reload reset load_target self unless @target.nil? end - # Has the target been already loaded? + # Has the \target been already \loaded? def loaded? @loaded end - # Asserts the target has been loaded setting the loaded flag to +true+. + # Asserts the \target has been loaded setting the \loaded flag to +true+. def loaded @loaded = true end @@ -128,13 +128,13 @@ module ActiveRecord @target end - # Sets the target of this proxy to +target+, and the loaded flag to +true+. + # Sets the target of this proxy to \target, and the \loaded flag to +true+. def target=(target) @target = target loaded end - # Forwards the call to the target. Loads the target if needed. + # Forwards the call to the target. Loads the \target if needed. def inspect load_target @target.inspect @@ -206,7 +206,7 @@ module ActiveRecord end private - # Forwards any missing method call to the target. + # Forwards any missing method call to the \target. def method_missing(method, *args) if load_target if block_given? @@ -217,16 +217,16 @@ module ActiveRecord end end - # Loads the target if needed and returns it. + # Loads the \target if needed and returns it. # # This method is abstract in the sense that it relies on +find_target+, # which is expected to be provided by descendants. # - # If the target is already loaded it is just returned. Thus, you can call - # +load_target+ unconditionally to get the target. + # If the \target is already \loaded it is just returned. Thus, you can call + # +load_target+ unconditionally to get the \target. # # ActiveRecord::RecordNotFound is rescued within the method, and it is - # not reraised. The proxy is reset and +nil+ is the return value. + # not reraised. The proxy is \reset and +nil+ is the return value. def load_target return nil unless defined?(@loaded) -- cgit v1.2.3 From 2ee60b0f70febc8c28a90e8361d9e9c2b86d4833 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 19 Aug 2008 02:51:37 +0200 Subject: added a class rdoc and documented count_records in has_many_association.rb --- .../lib/active_record/associations/has_many_association.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index e6fa15c173..f06e69aba3 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -1,5 +1,9 @@ module ActiveRecord module Associations + # This is the proxy that handles a has many association. + # + # If the association has a :through option further specialization + # is provided by its child HasManyThroughAssociation. class HasManyAssociation < AssociationCollection #:nodoc: # Count the number of associated records. All arguments are optional. def count(*args) @@ -27,6 +31,16 @@ module ActiveRecord end end + # Returns the number of records in this collection. + # + # If the association has a counter cache it gets that value. Otherwise + # a count via SQL is performed, bounded to :limit if there's one. + # That does not depend on whether the collection has already been loaded + # or not. The +size+ method is the one that takes the loaded flag into + # account and delegates to +count_records+ if needed. + # + # If the collection is empty the target is set to an empty array and + # the loaded flag is set to true as well. def count_records count = if has_cached_counter? @owner.send(:read_attribute, cached_counter_attribute_name) -- cgit v1.2.3 From 2009fb52061aca32c318dfa91d4fa5b59eb095c7 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 19 Aug 2008 16:25:31 +0200 Subject: revised rdoc of join_table_name --- activerecord/lib/active_record/associations.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 4e33dfe69f..8129d571a6 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1220,12 +1220,11 @@ module ActiveRecord end private - # Generate a join table name from two provided tables names. - # The order of names in join name is determined by lexical precedence. - # join_table_name("members", "clubs") - # => "clubs_members" - # join_table_name("members", "special_clubs") - # => "members_special_clubs" + # Generates a join table name from two provided table names. + # The names in the join table namesme end up in lexicographic order. + # + # join_table_name("members", "clubs") # => "clubs_members" + # join_table_name("members", "special_clubs") # => "members_special_clubs" def join_table_name(first_table_name, second_table_name) if first_table_name < second_table_name join_table = "#{first_table_name}_#{second_table_name}" -- cgit v1.2.3 From 69820504e0b9c453a636ecffdb3518cfca8b7857 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Thu, 21 Aug 2008 02:19:09 +0200 Subject: added class rdoc for AssociationCollection and revised the rdoc of a few methods --- .../associations/association_collection.rb | 36 +++++++++++++++++++--- 1 file changed, 31 insertions(+), 5 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 9061037b39..a12d4face4 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -2,6 +2,19 @@ require 'set' module ActiveRecord module Associations + # AssociationCollection is an abstract class that provides common stuff to + # ease the implementation of association proxies that represent + # collections. See the class hierarchy in AssociationProxy. + # + # You need to be careful with assumptions regarding the target: The proxy + # does not fetch records from the database until it needs them, but new + # ones created with +build+ are added to the target. So, the target may be + # non-empty and still lack children waiting to be read from the database. + # If you look directly to the database you cannot assume that's the entire + # collection because new records may have beed added to the target, etc. + # + # If you need to work on all current children, new and existing records, + # +load_target+ and the +loaded+ flag are your friends. class AssociationCollection < AssociationProxy #:nodoc: def initialize(owner, reflection) super @@ -185,9 +198,16 @@ module ActiveRecord end end - # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and - # calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero - # and you need to fetch that collection afterwards, it'll take one less SELECT query if you use length. + # Returns the size of the collection by executing a SELECT COUNT(*) + # query if the collection hasn't been loaded, and calling + # collection.size if it has. + # + # If the collection has been already loaded +size+ and +length+ are + # equivalent. If not and you are going to need the records anyway + # +length+ will take one less query. Otherwise +size+ is more efficient. + # + # This method is abstract in the sense that it relies on + # +count_records+, which is a method descendants have to provide. def size if @owner.new_record? || (loaded? && !@reflection.options[:uniq]) @target.size @@ -199,12 +219,18 @@ module ActiveRecord end end - # Returns the size of the collection by loading it and calling size on the array. If you want to use this method to check - # whether the collection is empty, use collection.length.zero? instead of collection.empty? + # Returns the size of the collection calling +size+ on the target. + # + # If the collection has been already loaded +length+ and +size+ are + # equivalent. If not and you are going to need the records anyway this + # method will take one less query. Otherwise +size+ is more efficient. def length load_target.size end + # Equivalent to collection.size.zero?. If the collection has + # not been already loaded and you are going to fetch the records anyway + # it is better to check collection.length.zero?. def empty? size.zero? end -- cgit v1.2.3 From 4e6f91402ce2dfa1b105212878209fa55f57cb6f Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sat, 23 Aug 2008 23:59:09 +0200 Subject: before_validation raises ActiveRecord::RecordInvalid instead of ActiveRecord::RecordNotSaved --- activerecord/lib/active_record/callbacks.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index f1983b6496..d99e183f9e 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -161,7 +161,7 @@ module ActiveRecord # == before_validation* returning statements # # If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be aborted and Base#save will return +false+. - # If Base#save! is called it will raise a RecordNotSaved exception. + # If Base#save! is called it will raise a ActiveRecord::RecordInvalid exception. # Nothing will be appended to the errors object. # # == Canceling callbacks -- cgit v1.2.3 From 4c86f074a7e3506886299c6290b539d1bc5f9d03 Mon Sep 17 00:00:00 2001 From: Zach Inglis Date: Sat, 23 Aug 2008 13:41:54 -0400 Subject: Corrected interpolation in a model find --- activerecord/lib/active_record/associations.rb | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 8129d571a6..88a7b52bfb 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -658,10 +658,15 @@ module ActiveRecord # (*Note*: +collection+ is replaced with the symbol passed as the first argument, so # has_many :clients would add among others clients.empty?.) # +<<<<<<< HEAD:activerecord/lib/active_record/associations.rb # === Example # # A Firm class declares has_many :clients, which will add: # * Firm#clients (similar to Clients.find :all, :conditions => "firm_id = #{id}") +======= + # Example: A Firm class declares has_many :clients, which will add: + # * Firm#clients (similar to Clients.find :all, :conditions => ["firm_id = ?", id]) +>>>>>>> Corrected interpolation in a model find:activerecord/lib/active_record/associations.rb # * Firm#clients<< # * Firm#clients.delete # * Firm#clients= -- cgit v1.2.3 From eaa9c064390c7c303be6e6571a14d7dd611ed07b Mon Sep 17 00:00:00 2001 From: Zach Inglis Date: Mon, 25 Aug 2008 14:22:40 -0400 Subject: fixed a silly mistake of not fixing a merge --- activerecord/lib/active_record/associations.rb | 5 ----- 1 file changed, 5 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 88a7b52bfb..eb1281901b 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -658,15 +658,10 @@ module ActiveRecord # (*Note*: +collection+ is replaced with the symbol passed as the first argument, so # has_many :clients would add among others clients.empty?.) # -<<<<<<< HEAD:activerecord/lib/active_record/associations.rb # === Example # - # A Firm class declares has_many :clients, which will add: - # * Firm#clients (similar to Clients.find :all, :conditions => "firm_id = #{id}") -======= # Example: A Firm class declares has_many :clients, which will add: # * Firm#clients (similar to Clients.find :all, :conditions => ["firm_id = ?", id]) ->>>>>>> Corrected interpolation in a model find:activerecord/lib/active_record/associations.rb # * Firm#clients<< # * Firm#clients.delete # * Firm#clients= -- cgit v1.2.3 From 5db2f199aba9aa8d00adefa8237922ad684aca03 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 26 Aug 2008 03:26:54 +0200 Subject: Reworded and corrected docs of AR#save and AR#save! --- activerecord/lib/active_record/base.rb | 42 ++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 12 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 8e40b331d9..b282ea931e 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2237,22 +2237,40 @@ module ActiveRecord #:nodoc: defined?(@new_record) && @new_record end - # * No record exists: Creates a new record with values matching those of the object attributes. - # * A record does exist: Updates the record with values matching those of the object attributes. - # - # Note: If your model specifies any validations then the method declaration dynamically - # changes to: - # save(perform_validation=true) - # Calling save(false) saves the model without running validations. - # See ActiveRecord::Validations for more information. + # :call-seq: + # save(perform_validation = true) + # + # Saves the model. + # + # If the model is new a record gets created in the database, otherwise + # the existing record gets updated. + # + # If +perform_validation+ is true validations run. If any of them fail + # the action is cancelled and +save+ returns +false+. If the flag is + # false validations are bypassed altogether. See + # ActiveRecord::Validations for more information. + # + # There's a series of callbacks associated with +save+. If any of the + # before_* callbacks return +false+ the action is cancelled and + # +save+ returns +false+. See ActiveRecord::Callbacks for further + # details. def save create_or_update end - # Attempts to save the record, but instead of just returning false if it couldn't happen, it raises an - # ActiveRecord::RecordNotSaved exception. However, if the callback chain raises ActiveRecord::Rollback - # to rollback the transaction that wraps save! no exception is raised, save! just - # returns +nil+. See ActiveRecord::Callbacks for further details. + # Saves the model. + # + # If the model is new a record gets created in the database, otherwise + # the existing record gets updated. + # + # With save! validations always run. If any of them fail + # ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations + # for more information. + # + # There's a series of callbacks associated with save!. If any of + # the before_* callbacks return +false+ the action is cancelled + # and save! raises ActiveRecord::RecordNotSaved. See + # ActiveRecord::Callbacks for further details. def save! create_or_update || raise(RecordNotSaved) end -- cgit v1.2.3