diff options
author | Jeremy Kemper <jeremy@bitsweat.net> | 2008-12-29 14:38:54 -0800 |
---|---|---|
committer | Jeremy Kemper <jeremy@bitsweat.net> | 2008-12-29 14:38:54 -0800 |
commit | 276ec16007b03d0a527fb0b83a7ee0b81e460fa1 (patch) | |
tree | 224491aa1948d613a551189028746d73400fccce /activerecord/lib/active_record | |
parent | 2e053aec9bafa8735d70886f36dea06ea10dc4ce (diff) | |
parent | 490c26c8433a6d278bc61118782da360e8889646 (diff) | |
download | rails-276ec16007b03d0a527fb0b83a7ee0b81e460fa1.tar.gz rails-276ec16007b03d0a527fb0b83a7ee0b81e460fa1.tar.bz2 rails-276ec16007b03d0a527fb0b83a7ee0b81e460fa1.zip |
Merge branch 'master' of git@github.com:rails/rails
Diffstat (limited to 'activerecord/lib/active_record')
12 files changed, 224 insertions, 107 deletions
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index 7b1b2d9ad9..9d0bf3a308 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -43,7 +43,7 @@ module ActiveRecord # loading in a more high-level (application developer-friendly) manner. module ClassMethods protected - + # Eager loads the named associations for the given ActiveRecord record(s). # # In this description, 'association name' shall refer to the name passed @@ -94,8 +94,8 @@ module ActiveRecord raise "parent must be an association name" unless parent.is_a?(String) || parent.is_a?(Symbol) preload_associations(records, parent, preload_options) reflection = reflections[parent] - parents = records.map {|record| record.send(reflection.name)}.flatten - unless parents.empty? || parents.first.nil? + parents = records.map {|record| record.send(reflection.name)}.flatten.compact + unless parents.empty? parents.first.class.preload_associations(parents, child) end end @@ -113,7 +113,7 @@ module ActiveRecord # unnecessarily records.group_by {|record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, records| raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection - + # 'reflection.macro' can return 'belongs_to', 'has_many', etc. Thus, # the following could call 'preload_belongs_to_association', # 'preload_has_many_association', etc. @@ -128,7 +128,7 @@ module ActiveRecord association_proxy.target.push(*[associated_record].flatten) end end - + def add_preloaded_record_to_collection(parent_records, reflection_name, associated_record) parent_records.each do |parent_record| parent_record.send("set_#{reflection_name}_target", associated_record) @@ -183,18 +183,19 @@ module ActiveRecord conditions = "t0.#{reflection.primary_key_name} #{in_or_equals_for_ids(ids)}" conditions << append_conditions(reflection, preload_options) - associated_records = reflection.klass.find(:all, :conditions => [conditions, ids], - :include => options[:include], - :joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}", - :select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id", - :order => options[:order]) - + associated_records = reflection.klass.with_exclusive_scope do + reflection.klass.find(:all, :conditions => [conditions, ids], + :include => options[:include], + :joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}", + :select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id", + :order => options[:order]) + end set_association_collection_records(id_to_record_map, reflection.name, associated_records, 'the_parent_record_id') end def preload_has_one_association(records, reflection, preload_options={}) return if records.first.send("loaded_#{reflection.name}?") - id_to_record_map, ids = construct_id_map(records) + id_to_record_map, ids = construct_id_map(records) options = reflection.options records.each {|record| record.send("set_#{reflection.name}_target", nil)} if options[:through] @@ -248,7 +249,7 @@ module ActiveRecord reflection.primary_key_name) end end - + def preload_through_records(records, reflection, through_association) through_reflection = reflections[through_association] through_primary_key = through_reflection.primary_key_name @@ -333,11 +334,13 @@ module ActiveRecord end conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} #{in_or_equals_for_ids(ids)}" conditions << append_conditions(reflection, preload_options) - associated_records = klass.find(:all, :conditions => [conditions, ids], + associated_records = klass.with_exclusive_scope do + klass.find(:all, :conditions => [conditions, ids], :include => options[:include], :select => options[:select], :joins => options[:joins], :order => options[:order]) + end set_association_single_records(id_map, reflection.name, associated_records, primary_key) end end @@ -355,13 +358,15 @@ module ActiveRecord conditions << append_conditions(reflection, preload_options) - reflection.klass.find(:all, + reflection.klass.with_exclusive_scope do + reflection.klass.find(:all, :select => (preload_options[:select] || options[:select] || "#{table_name}.*"), :include => preload_options[:include] || options[:include], :conditions => [conditions, ids], :joins => options[:joins], :group => preload_options[:group] || options[:group], :order => preload_options[:order] || options[:order]) + end end diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 07bc50c886..c154a5087c 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1216,11 +1216,11 @@ module ActiveRecord # callbacks will be executed after the association is wiped out. old_method = "destroy_without_habtm_shim_for_#{reflection.name}" class_eval <<-end_eval unless method_defined?(old_method) - alias_method :#{old_method}, :destroy_without_callbacks - def destroy_without_callbacks - #{reflection.name}.clear - #{old_method} - end + alias_method :#{old_method}, :destroy_without_callbacks # alias_method :destroy_without_habtm_shim_for_posts, :destroy_without_callbacks + def destroy_without_callbacks # def destroy_without_callbacks + #{reflection.name}.clear # posts.clear + #{old_method} # destroy_without_habtm_shim_for_posts + end # end end_eval add_association_callbacks(reflection.name, options) @@ -1453,7 +1453,7 @@ module ActiveRecord dependent_conditions << sanitize_sql(reflection.options[:conditions]) 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('@', '\@') case reflection.options[:dependent] when :destroy method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym @@ -1463,22 +1463,22 @@ module ActiveRecord before_destroy method_name when :delete_all module_eval %Q{ - before_destroy do |record| - delete_all_has_many_dependencies(record, - "#{reflection.name}", - #{reflection.class_name}, - "#{dependent_conditions}") - end + before_destroy do |record| # before_destroy do |record| + delete_all_has_many_dependencies(record, # delete_all_has_many_dependencies(record, + "#{reflection.name}", # "posts", + #{reflection.class_name}, # Post, + %@#{dependent_conditions}@) # %@...@) # this is a string literal like %(...) + end # end } when :nullify module_eval %Q{ - before_destroy do |record| - nullify_has_many_dependencies(record, - "#{reflection.name}", - #{reflection.class_name}, - "#{reflection.primary_key_name}", - "#{dependent_conditions}") - end + before_destroy do |record| # before_destroy do |record| + nullify_has_many_dependencies(record, # nullify_has_many_dependencies(record, + "#{reflection.name}", # "posts", + #{reflection.class_name}, # Post, + "#{reflection.primary_key_name}", # "user_id", + %@#{dependent_conditions}@) # %@...@) # this is a string literal like %(...) + end # end } else raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, or :nullify (#{reflection.options[:dependent].inspect})" diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index aa6013583d..cca012ed55 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1456,7 +1456,10 @@ module ActiveRecord #:nodoc: def respond_to?(method_id, include_private = false) if match = DynamicFinderMatch.match(method_id) return true if all_attributes_exists?(match.attribute_names) + elsif match = DynamicScopeMatch.match(method_id) + return true if all_attributes_exists?(match.attribute_names) end + super end @@ -1494,11 +1497,16 @@ module ActiveRecord #:nodoc: end if scoped?(:find, :order) - scoped_order = reverse_sql_order(scope(:find, :order)) - scoped_methods.select { |s| s[:find].update(:order => scoped_order) } + scope = scope(:find) + original_scoped_order = scope[:order] + scope[:order] = reverse_sql_order(original_scoped_order) end - find_initial(options.merge({ :order => order })) + begin + find_initial(options.merge({ :order => order })) + ensure + scope[:order] = original_scoped_order if original_scoped_order + end end def reverse_sql_order(order_query) @@ -1804,7 +1812,11 @@ module ActiveRecord #:nodoc: # This also enables you to initialize a record if it is not found, such as find_or_initialize_by_amount(amount) # or find_or_create_by_user_and_password(user, password). # - # Each dynamic finder or initializer/creator is also defined in the class after it is first invoked, so that future + # Also enables dynamic scopes like scoped_by_user_name(user_name) and scoped_by_user_name_and_password(user_name, password) that + # are turned into scoped(:conditions => ["user_name = ?", user_name]) and scoped(:conditions => ["user_name = ? AND password = ?", user_name, password]) + # respectively. + # + # Each dynamic finder, scope or initializer/creator is also defined in the class after it is first invoked, so that future # attempts to use it do not run through method_missing. def method_missing(method_id, *arguments, &block) if match = DynamicFinderMatch.match(method_id) @@ -1813,10 +1825,31 @@ module ActiveRecord #:nodoc: 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) + attributes = construct_attributes_from_arguments( + [:#{attribute_names.join(',:')}], + args + ) finder_options = { :conditions => attributes } validate_find_options(options) set_readonly_option!(options) @@ -1834,6 +1867,31 @@ module ActiveRecord #:nodoc: send(method_id, *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 @@ -1863,6 +1921,22 @@ module ActiveRecord #:nodoc: }, __FILE__, __LINE__ send(method_id, *arguments, &block) end + elsif match = DynamicScopeMatch.match(method_id) + attribute_names = match.attribute_names + super unless all_attributes_exists?(attribute_names) + if match.scope? + self.class_eval %{ + def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args) + options = args.extract_options! # options = args.extract_options! + attributes = construct_attributes_from_arguments( # attributes = construct_attributes_from_arguments( + [:#{attribute_names.join(',:')}], args # [:user_name, :password], args + ) # ) + # + scoped(:conditions => attributes) # scoped(:conditions => attributes) + end # end + }, __FILE__, __LINE__ + send(method_id, *arguments) + end else super end @@ -2401,9 +2475,9 @@ module ActiveRecord #:nodoc: write_attribute(self.class.primary_key, value) end - # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet. + # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false. def new_record? - defined?(@new_record) && @new_record + @new_record || false end # :call-seq: @@ -3010,7 +3084,7 @@ module ActiveRecord #:nodoc: end Base.class_eval do - extend QueryCache + extend QueryCache::ClassMethods include Validations include Locking::Optimistic, Locking::Pessimistic include AttributeMethods diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index 950bd72101..00c71090f3 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -14,12 +14,12 @@ module ActiveRecord def dirties_query_cache(base, *method_names) method_names.each do |method_name| base.class_eval <<-end_code, __FILE__, __LINE__ - def #{method_name}_with_query_dirty(*args) - clear_query_cache if @query_cache_enabled - #{method_name}_without_query_dirty(*args) - end - - alias_method_chain :#{method_name}, :query_dirty + def #{method_name}_with_query_dirty(*args) # def update_with_query_dirty(*args) + clear_query_cache if @query_cache_enabled # clear_query_cache if @query_cache_enabled + #{method_name}_without_query_dirty(*args) # update_without_query_dirty(*args) + end # end + # + alias_method_chain :#{method_name}, :query_dirty # alias_method_chain :update, :query_dirty end_code end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index fe9cbcf024..273f823e7f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -476,12 +476,12 @@ module ActiveRecord %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type| class_eval <<-EOV - def #{column_type}(*args) - options = args.extract_options! - column_names = args - - column_names.each { |name| column(name, '#{column_type}', options) } - end + def #{column_type}(*args) # def string(*args) + options = args.extract_options! # options = args.extract_options! + column_names = args # column_names = args + # + column_names.each { |name| column(name, '#{column_type}', options) } # column_names.each { |name| column(name, 'string', options) } + end # end EOV end @@ -676,24 +676,24 @@ module ActiveRecord # t.string(:goat, :sheep) %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type| class_eval <<-EOV - def #{column_type}(*args) - options = args.extract_options! - column_names = args - - column_names.each do |name| - column = ColumnDefinition.new(@base, name, '#{column_type}') - if options[:limit] - column.limit = options[:limit] - elsif native['#{column_type}'.to_sym].is_a?(Hash) - column.limit = native['#{column_type}'.to_sym][:limit] - end - column.precision = options[:precision] - column.scale = options[:scale] - column.default = options[:default] - column.null = options[:null] - @base.add_column(@table_name, name, column.sql_type, options) - end - end + def #{column_type}(*args) # def string(*args) + options = args.extract_options! # options = args.extract_options! + column_names = args # column_names = args + # + column_names.each do |name| # column_names.each do |name| + column = ColumnDefinition.new(@base, name, '#{column_type}') # column = ColumnDefinition.new(@base, name, 'string') + if options[:limit] # if options[:limit] + column.limit = options[:limit] # column.limit = options[:limit] + elsif native['#{column_type}'.to_sym].is_a?(Hash) # elsif native['string'.to_sym].is_a?(Hash) + column.limit = native['#{column_type}'.to_sym][:limit] # column.limit = native['string'.to_sym][:limit] + end # end + column.precision = options[:precision] # column.precision = options[:precision] + column.scale = options[:scale] # column.scale = options[:scale] + column.default = options[:default] # column.default = options[:default] + column.null = options[:null] # column.null = options[:null] + @base.add_column(@table_name, name, column.sql_type, options) # @base.add_column(@table_name, name, column.sql_type, options) + end # end + end # end EOV end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 46d4b6c89c..60729c63db 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -13,23 +13,25 @@ module MysqlCompat #:nodoc: # C driver >= 2.7 returns null values in each_hash if Mysql.const_defined?(:VERSION) && (Mysql::VERSION.is_a?(String) || Mysql::VERSION >= 20700) target.class_eval <<-'end_eval' - def all_hashes - rows = [] - each_hash { |row| rows << row } - rows - end + def all_hashes # def all_hashes + rows = [] # rows = [] + each_hash { |row| rows << row } # each_hash { |row| rows << row } + rows # rows + end # end end_eval # adapters before 2.7 don't have a version constant # and don't return null values in each_hash else target.class_eval <<-'end_eval' - def all_hashes - rows = [] - all_fields = fetch_fields.inject({}) { |fields, f| fields[f.name] = nil; fields } - each_hash { |row| rows << all_fields.dup.update(row) } - rows - end + def all_hashes # def all_hashes + rows = [] # rows = [] + all_fields = fetch_fields.inject({}) { |fields, f| # all_fields = fetch_fields.inject({}) { |fields, f| + fields[f.name] = nil; fields # fields[f.name] = nil; fields + } # } + each_hash { |row| rows << all_fields.dup.update(row) } # each_hash { |row| rows << all_fields.dup.update(row) } + rows # rows + end # end end_eval end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 60ec01b95e..6685cb8663 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -950,13 +950,13 @@ module ActiveRecord # should know about this but can't detect it there, so deal with it here. money_precision = (postgresql_version >= 80300) ? 19 : 10 PostgreSQLColumn.module_eval(<<-end_eval) - def extract_precision(sql_type) - if sql_type =~ /^money$/ - #{money_precision} - else - super - end - end + def extract_precision(sql_type) # def extract_precision(sql_type) + if sql_type =~ /^money$/ # if sql_type =~ /^money$/ + #{money_precision} # 19 + else # else + super # super + end # end + end # end end_eval configure_connection diff --git a/activerecord/lib/active_record/dirty.rb b/activerecord/lib/active_record/dirty.rb index a1760875ba..4c899f58e5 100644 --- a/activerecord/lib/active_record/dirty.rb +++ b/activerecord/lib/active_record/dirty.rb @@ -174,7 +174,7 @@ module ActiveRecord alias_attribute_without_dirty(new_name, old_name) DIRTY_SUFFIXES.each do |suffix| module_eval <<-STR, __FILE__, __LINE__+1 - def #{new_name}#{suffix}; self.#{old_name}#{suffix}; end + def #{new_name}#{suffix}; self.#{old_name}#{suffix}; end # def subject_changed?; self.title_changed?; end STR end end diff --git a/activerecord/lib/active_record/dynamic_scope_match.rb b/activerecord/lib/active_record/dynamic_scope_match.rb new file mode 100644 index 0000000000..f796ba669a --- /dev/null +++ b/activerecord/lib/active_record/dynamic_scope_match.rb @@ -0,0 +1,25 @@ +module ActiveRecord + class DynamicScopeMatch + def self.match(method) + ds_match = self.new(method) + ds_match.scope ? ds_match : nil + end + + def initialize(method) + @scope = true + case method.to_s + when /^scoped_by_([_a-zA-Z]\w*)$/ + names = $1 + else + @scope = nil + end + @attribute_names = names && names.split('_and_') + end + + attr_reader :scope, :attribute_names + + def scope? + !@scope.nil? + end + end +end diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb index a8af89fcb9..eb92bc2545 100644 --- a/activerecord/lib/active_record/query_cache.rb +++ b/activerecord/lib/active_record/query_cache.rb @@ -1,20 +1,32 @@ module ActiveRecord - module QueryCache - # Enable the query cache within the block if Active Record is configured. - def cache(&block) - if ActiveRecord::Base.configurations.blank? - yield - else - connection.cache(&block) + class QueryCache + module ClassMethods + # Enable the query cache within the block if Active Record is configured. + def cache(&block) + if ActiveRecord::Base.configurations.blank? + yield + else + connection.cache(&block) + end end + + # Disable the query cache within the block if Active Record is configured. + def uncached(&block) + if ActiveRecord::Base.configurations.blank? + yield + else + connection.uncached(&block) + end + end + end + + def initialize(app) + @app = app end - # Disable the query cache within the block if Active Record is configured. - def uncached(&block) - if ActiveRecord::Base.configurations.blank? - yield - else - connection.uncached(&block) + def call(env) + ActiveRecord::Base.cache do + @app.call(env) end end end diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index a9e0efa6fe..8dbe80a01a 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -23,8 +23,8 @@ module ActiveRecord write_attribute('created_at', t) if respond_to?(:created_at) && created_at.nil? write_attribute('created_on', t) if respond_to?(:created_on) && created_on.nil? - write_attribute('updated_at', t) if respond_to?(:updated_at) - write_attribute('updated_on', t) if respond_to?(:updated_on) + write_attribute('updated_at', t) if respond_to?(:updated_at) && updated_at.nil? + write_attribute('updated_on', t) if respond_to?(:updated_on) && updated_on.nil? end create_without_timestamps end diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 617b3f440f..6a9690ba85 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -203,7 +203,6 @@ module ActiveRecord if attr == "base" full_messages << message else - #key = :"activerecord.att.#{@base.class.name.underscore.to_sym}.#{attr}" attr_name = @base.class.human_attribute_name(attr) full_messages << attr_name + I18n.t('activerecord.errors.format.separator', :default => ' ') + message end |