From d5dc02b5e88324bdbd274a5008a1d6b7a2f6f9d7 Mon Sep 17 00:00:00 2001 From: ozzyaaron Date: Tue, 29 Mar 2011 11:22:16 +0800 Subject: Added back the Callback debugging section by interrogating the _*_callbacks method --- activerecord/lib/active_record/callbacks.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 86d58df99b..a175bf003c 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -214,6 +214,24 @@ module ActiveRecord # needs to be aware of it because an ordinary +save+ will raise such exception # instead of quietly returning +false+. # + # == Debugging callbacks + # + # The callback chain is accessible via the _*_callbacks method on an object. ActiveModel Callbacks support + # :before, :after and :around as values for the kind property. The kind property + # defines what part of the chain the callback runs in. + # + # To find all callbacks in the before_save callback chain: + # + # Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) } + # + # Returns an array of callback objects that form the before_save chain. + # + # To further check if the before_save chain contains a proc defined as rest_when_dead use the filter property of the callback object: + # + # Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }.collect(&:filter).include?(:rest_when_dead) + # + # Returns true or false depending on whether the proc is contained in the before_save callback chain on a Topic model. + # module Callbacks extend ActiveSupport::Concern -- cgit v1.2.3 From 45d5d6b2683be263ae9c977324633972f318b814 Mon Sep 17 00:00:00 2001 From: Eadz Date: Sat, 19 Mar 2011 00:00:50 -0700 Subject: Documented undocumented feature: Class methods on your model are automatically available on scopes --- activerecord/lib/active_record/named_scope.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index d291632260..a445f68790 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -99,6 +99,28 @@ module ActiveRecord # # Article.published.new.published # => true # Article.published.create.published # => true + # + # Class methods on your model are automatically available + # on scopes + # + # class Article < ActiveRecord::Base + # scope :pubished, where(:published => true) + # scope :featured, where(:featured => true) + # + # def self.latest_article + # order('published_at desc').first + # end + # + # def self.titles + # map{|article| article.title} + # end + # + # end + # + # Example usage: + # Article.published.featured.latest_article + # Article.featured.titles + def scope(name, scope_options = {}) name = name.to_sym valid_scope_name?(name) -- cgit v1.2.3 From 6a1715111e16e07a30bd61eaecf059fd90732e59 Mon Sep 17 00:00:00 2001 From: Eadz Date: Sat, 19 Mar 2011 00:02:53 -0700 Subject: add space to conform with style --- activerecord/lib/active_record/named_scope.rb | 2 +- 1 file changed, 1 insertion(+), 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 a445f68790..9c0652c3a0 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -112,7 +112,7 @@ module ActiveRecord # end # # def self.titles - # map{|article| article.title} + # map {|article| article.title} # end # # end -- cgit v1.2.3 From 7a34ab7d60756856b79d2f8ef33ac843a78b70ad Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Thu, 31 Mar 2011 06:46:02 +1100 Subject: Fix typo in named_scope documentation --- activerecord/lib/active_record/named_scope.rb | 2 +- 1 file changed, 1 insertion(+), 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 9c0652c3a0..603a0c169a 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -104,7 +104,7 @@ module ActiveRecord # on scopes # # class Article < ActiveRecord::Base - # scope :pubished, where(:published => true) + # scope :published, where(:published => true) # scope :featured, where(:featured => true) # # def self.latest_article -- cgit v1.2.3 From 04d5decfd3c8f899df462bfc7f1ccb9770542a97 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Thu, 31 Mar 2011 06:47:01 +1100 Subject: Cleanup of named_scope documentation --- activerecord/lib/active_record/named_scope.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 603a0c169a..8eb87f7b7a 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -101,24 +101,25 @@ module ActiveRecord # Article.published.create.published # => true # # Class methods on your model are automatically available - # on scopes - # + # on scopes. Assuming the following setup: + # # class Article < ActiveRecord::Base # scope :published, where(:published => true) # scope :featured, where(:featured => true) - # + # # def self.latest_article - # order('published_at desc').first + # order('published_at desc').first # end - # + # # def self.titles # map {|article| article.title} # end # # end - # - # Example usage: - # Article.published.featured.latest_article + # + # We are able to call the methods like this: + # + # Article.published.featured.latest_article # Article.featured.titles def scope(name, scope_options = {}) -- cgit v1.2.3 From cf07da0929bbeaaeb68cbafbb600727b3bda470e Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Thu, 31 Mar 2011 06:59:48 +1100 Subject: Symbol to proc is preferred over longer form of map --- activerecord/lib/active_record/named_scope.rb | 2 +- 1 file changed, 1 insertion(+), 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 8eb87f7b7a..d5fff65303 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -112,7 +112,7 @@ module ActiveRecord # end # # def self.titles - # map {|article| article.title} + # map(&:title) # end # # end -- cgit v1.2.3 From bd3cdeea354ebff97b0d5102a0857ce85eedcfa4 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Sun, 3 Apr 2011 12:47:51 +0900 Subject: s/ERb/ERB/g The author of ERB sais, his eRuby implementation was originally named "ERb/ERbLight" and then renamed to "ERB" when started bundled as a Ruby standard lib. http://www2a.biglobe.ne.jp/~seki/ruby/erb.html --- activerecord/lib/active_record/fixtures.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index d523c643ba..0939ec2626 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -173,10 +173,10 @@ class FixturesFileNotFound < StandardError; end # traversed in the database to create the fixture hash and/or instance variables. This is expensive for # large sets of fixtured data. # -# = Dynamic fixtures with ERb +# = Dynamic fixtures with ERB # # Some times you don't care about the content of the fixtures as much as you care about the volume. In these cases, you can -# mix ERb in with your YAML or CSV fixtures to create a bunch of fixtures for load testing, like: +# mix ERB in with your YAML or CSV fixtures to create a bunch of fixtures for load testing, like: # # <% for i in 1..1000 %> # fix_<%= i %>: @@ -186,7 +186,7 @@ class FixturesFileNotFound < StandardError; end # # This will create 1000 very simple YAML fixtures. # -# Using ERb, you can also inject dynamic values into your fixtures with inserts like <%= Date.today.strftime("%Y-%m-%d") %>. +# Using ERB, you can also inject dynamic values into your fixtures with inserts like <%= Date.today.strftime("%Y-%m-%d") %>. # This is however a feature to be used with some caution. The point of fixtures are that they're # stable units of predictable sample data. If you feel that you need to inject dynamic values, then # perhaps you should reexamine whether your application is properly testable. Hence, dynamic values -- cgit v1.2.3 From b35617235d43bdb32016a623044e7f4005879969 Mon Sep 17 00:00:00 2001 From: Emilio Tagua Date: Sun, 3 Apr 2011 20:09:00 -0300 Subject: Use IM when trying to load records using ID. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activerecord/lib/active_record/relation.rb | 3 +++ activerecord/lib/active_record/relation/finder_methods.rb | 9 +++++++++ activerecord/lib/active_record/transactions.rb | 1 + 3 files changed, 13 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 896daf516e..75269c85d9 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -194,6 +194,7 @@ module ActiveRecord # # The same idea applies to limit and order # Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(:author => 'David') def update_all(updates, conditions = nil, options = {}) + IdentityMap.repository[symbolized_base_class].clear if IdentityMap.enabled? if conditions || options.present? where(conditions).apply_finder_options(options.slice(:limit, :order)).update_all(updates) else @@ -322,6 +323,7 @@ module ActiveRecord # 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) + IdentityMap.repository[symbolized_base_class] = {} if IdentityMap.enabled? if conditions where(conditions).delete_all else @@ -353,6 +355,7 @@ module ActiveRecord # # Delete multiple rows # Todo.delete([2,3,4]) def delete(id_or_array) + IdentityMap.remove_by_id(self.symbolized_base_class, id_or_array) if IdentityMap.enabled? where(primary_key => id_or_array).delete_all end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 8fa315bdf3..7fe6fe0ed0 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -309,6 +309,15 @@ module ActiveRecord def find_one(id) id = id.id if ActiveRecord::Base === id + if IdentityMap.enabled? && where_values.blank? && + limit_value.blank? && order_values.blank? && + includes_values.blank? && preload_values.blank? && + readonly_value.nil? && joins_values.blank? && + !@klass.locking_enabled? && + record = IdentityMap.get(@klass, id) + return record + end + column = columns_hash[primary_key] substitute = connection.substitute_for(column, @bind_values) diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 60d4c256c4..d363f36108 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -270,6 +270,7 @@ module ActiveRecord def rolledback!(force_restore_state = false) #:nodoc: run_callbacks :rollback ensure + IdentityMap.remove(self) if IdentityMap.enabled? restore_transaction_record_state(force_restore_state) end -- cgit v1.2.3 From 454ec93ff79af4c0ddb31d01827021b549d12413 Mon Sep 17 00:00:00 2001 From: Emilio Tagua Date: Sun, 3 Apr 2011 20:19:52 -0300 Subject: Add log message when loading records from Identity Map. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activerecord/lib/active_record/identity_map.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/identity_map.rb b/activerecord/lib/active_record/identity_map.rb index d18b2b0a54..95a8e5cff7 100644 --- a/activerecord/lib/active_record/identity_map.rb +++ b/activerecord/lib/active_record/identity_map.rb @@ -50,7 +50,14 @@ module ActiveRecord def get(klass, primary_key) obj = repository[klass.symbolized_base_class][primary_key] - obj.is_a?(klass) ? obj : nil + if obj.is_a?(klass) + if ActiveRecord::Base.logger + ActiveRecord::Base.logger.debug "#{klass} with ID = #{primary_key} loaded from Identity Map" + end + obj + else + nil + end end def add(record) -- cgit v1.2.3 From a9b4b5da7c216e4464eeb9dbd0a39ea258d64325 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Tue, 5 Apr 2011 11:01:04 +0200 Subject: Destroying records via nested attributes works independent of reject_if: - When a :_destroy truthiness is provided in the attributes hash, the record should get destroyed regardless of the result of the proc or method supplied to :reject_if. (If :allow_destroy is true) [#6006 state:committed] Signed-off-by: Santiago Pastorino --- activerecord/lib/active_record/nested_attributes.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 522c0cfc9f..c111a968dc 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -452,6 +452,7 @@ module ActiveRecord end def call_reject_if(association_name, attributes) + return false if has_destroy_flag?(attributes) case callback = self.nested_attributes_options[association_name][:reject_if] when Symbol method(callback).arity == 0 ? send(callback) : send(callback, attributes) -- cgit v1.2.3 From 62b2755f7a4ca12491a7899348066695a6a26917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 10 Apr 2011 13:03:49 +0200 Subject: Remove dead branch code that appeared back in a merge. --- activerecord/lib/active_record/nested_attributes.rb | 6 ------ 1 file changed, 6 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index c111a968dc..08b27b6a8e 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -403,12 +403,6 @@ module ActiveRecord unless reject_new_record?(association_name, attributes) association.build(attributes.except(*UNASSIGNABLE_KEYS)) end - elsif existing_records.count == 0 #Existing record but not yet associated - existing_record = self.class.reflect_on_association(association_name).klass.find(attributes['id']) - if !call_reject_if(association_name, attributes) - association.send(:add_record_to_target_with_callbacks, existing_record) if !association.loaded? - assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) - end elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s } unless association.loaded? || call_reject_if(association_name, attributes) # Make sure we are operating on the actual object which is in the association's -- cgit v1.2.3 From a9f3c9da01d721963d05949604ead909aaabbf36 Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Mon, 11 Apr 2011 00:52:42 +0800 Subject: Using Object#in? and Object#either? in various places There're a lot of places in Rails source code which make a lot of sense to switching to Object#in? or Object#either? instead of using [].include?. --- activerecord/lib/active_record/associations/association.rb | 3 ++- activerecord/lib/active_record/associations/builder/belongs_to.rb | 4 +++- activerecord/lib/active_record/associations/builder/has_many.rb | 4 +++- activerecord/lib/active_record/associations/builder/has_one.rb | 4 +++- activerecord/lib/active_record/associations/has_one_association.rb | 4 +++- .../lib/active_record/attribute_methods/time_zone_conversion.rb | 3 ++- activerecord/lib/active_record/railties/databases.rake | 4 +++- activerecord/lib/active_record/reflection.rb | 3 ++- .../active_record/session_migration/session_migration_generator.rb | 3 ++- 9 files changed, 23 insertions(+), 9 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 27c446b12c..b250783d9e 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/array/wrap' +require 'active_support/core_ext/object/inclusion' module ActiveRecord module Associations @@ -163,7 +164,7 @@ module ActiveRecord def creation_attributes attributes = {} - if [:has_one, :has_many].include?(reflection.macro) && !options[:through] + if reflection.macro.either?(:has_one, :has_many) && !options[:through] attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key] if reflection.options[:as] diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 964e7fddc8..9ea37da78f 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/object/inclusion' + module ActiveRecord::Associations::Builder class BelongsTo < SingularAssociation #:nodoc: self.macro = :belongs_to @@ -65,7 +67,7 @@ module ActiveRecord::Associations::Builder def configure_dependency if options[:dependent] - unless [:destroy, :delete].include?(options[:dependent]) + unless options[:dependent].either?(:destroy, :delete) raise ArgumentError, "The :dependent option expects either :destroy or :delete (#{options[:dependent].inspect})" end diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb index 77bb66228d..3b489d9647 100644 --- a/activerecord/lib/active_record/associations/builder/has_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_many.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/object/inclusion' + module ActiveRecord::Associations::Builder class HasMany < CollectionAssociation #:nodoc: self.macro = :has_many @@ -14,7 +16,7 @@ module ActiveRecord::Associations::Builder def configure_dependency if options[:dependent] - unless [:destroy, :delete_all, :nullify, :restrict].include?(options[:dependent]) + unless options[:dependent].either?(:destroy, :delete_all, :nullify, :restrict) raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, " \ ":nullify or :restrict (#{options[:dependent].inspect})" end diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb index 07ba5d088e..7efe2ec74d 100644 --- a/activerecord/lib/active_record/associations/builder/has_one.rb +++ b/activerecord/lib/active_record/associations/builder/has_one.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/object/inclusion' + module ActiveRecord::Associations::Builder class HasOne < SingularAssociation #:nodoc: self.macro = :has_one @@ -27,7 +29,7 @@ module ActiveRecord::Associations::Builder def configure_dependency if options[:dependent] - unless [:destroy, :delete, :nullify, :restrict].include?(options[:dependent]) + unless options[:dependent].either?(:destroy, :delete, :nullify, :restrict) raise ArgumentError, "The :dependent option expects either :destroy, :delete, " \ ":nullify or :restrict (#{options[:dependent].inspect})" end diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 1d2e8667e4..82f5696ad8 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/object/inclusion' + module ActiveRecord # = Active Record Belongs To Has One Association module Associations @@ -50,7 +52,7 @@ module ActiveRecord end def remove_target!(method) - if [:delete, :destroy].include?(method) + if method.either?(:delete, :destroy) target.send(method) else nullify_owner_attributes(target) diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index 6aac96df6f..032a3135f0 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/class/attribute' +require 'active_support/core_ext/object/inclusion' module ActiveRecord module AttributeMethods @@ -58,7 +59,7 @@ module ActiveRecord private def create_time_zone_conversion_attribute?(name, column) - time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp].include?(column.type) + time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) && column.type.either?(:datetime, :timestamp) end end end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index ff36814684..a1b0270d1b 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -1,3 +1,5 @@ +require 'active_support/core_ext/object/inclusion' + db_namespace = namespace :db do task :load_config => :rails_env do require 'active_record' @@ -135,7 +137,7 @@ db_namespace = namespace :db do end def local_database?(config, &block) - if %w( 127.0.0.1 localhost ).include?(config['host']) || config['host'].blank? + if config['host'].either?("127.0.0.1", "localhost") || config['host'].blank? yield else $stderr.puts "This task only modifies local databases. #{config['database']} is on a remote host." diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index e801bc4afa..82561e86cf 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/module/deprecation' +require 'active_support/core_ext/object/inclusion' module ActiveRecord # = Active Record Reflection @@ -163,7 +164,7 @@ module ActiveRecord def initialize(macro, name, options, active_record) super - @collection = [:has_many, :has_and_belongs_to_many].include?(macro) + @collection = macro.either?(:has_many, :has_and_belongs_to_many) end # Returns a new, unsaved instance of the associated class. +options+ will diff --git a/activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb b/activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb index afcda2a98a..e803ee5764 100644 --- a/activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +++ b/activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb @@ -1,4 +1,5 @@ require 'rails/generators/active_record' +require 'active_support/core_ext/object/inclusion' module ActiveRecord module Generators @@ -13,7 +14,7 @@ module ActiveRecord def session_table_name current_table_name = ActiveRecord::SessionStore::Session.table_name - if ["sessions", "session"].include?(current_table_name) + if current_table_name.either?("sessions", "session") current_table_name = (ActiveRecord::Base.pluralize_table_names ? 'session'.pluralize : 'session') end current_table_name -- cgit v1.2.3 From a30b440debe87722c0c072555db7945181ceffde Mon Sep 17 00:00:00 2001 From: Brian Cardarella Date: Sat, 19 Mar 2011 13:28:36 -0400 Subject: Refactored uniqueness validator to use Arel instead of hardcoded SQL --- .../lib/active_record/validations/uniqueness.rb | 37 +++++++--------------- 1 file changed, 11 insertions(+), 26 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 9cd6c26322..37381d063d 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -14,6 +14,7 @@ module ActiveRecord def validate_each(record, attribute, value) finder_class = find_finder_class_for(record) + table = finder_class.arel_table coder = record.class.serialized_attributes[attribute.to_s] @@ -21,21 +22,15 @@ module ActiveRecord value = coder.dump value end - sql, params = mount_sql_and_params(finder_class, record.class.quoted_table_name, attribute, value) - - relation = finder_class.unscoped.where(sql, *params) + relation = build_relation(finder_class, table, attribute, value) + relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.send(:id))) if record.persisted? Array.wrap(options[:scope]).each do |scope_item| scope_value = record.send(scope_item) - relation = relation.where(scope_item => scope_value) - end - - if record.persisted? - # TODO : This should be in Arel - relation = relation.where("#{record.class.quoted_table_name}.#{record.class.primary_key} <> ?", record.send(:id)) + relation = relation.and(table[scope_item].eq(scope_value)) end - if relation.exists? + if finder_class.unscoped.where(relation).exists? record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value)) end end @@ -57,27 +52,17 @@ module ActiveRecord class_hierarchy.detect { |klass| !klass.abstract_class? } end - def mount_sql_and_params(klass, table_name, attribute, value) #:nodoc: + def build_relation(klass, table, attribute, value) #:nodoc: column = klass.columns_hash[attribute.to_s] + value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s if column.text? - 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}" + if !options[:case_sensitive] && column.text? + relation = table[attribute].matches(value) else - sql = "LOWER(#{sql_attribute}) = LOWER(?)" + relation = table[attribute].eq(value) end - [sql, [value]] + relation end end -- cgit v1.2.3 From 35dba50be9f9641ba757e6253b6e39f4b92dd37f Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sun, 10 Apr 2011 22:30:27 -0700 Subject: community support for pg < 8.2 has ended, so we can drop support for those versions --- .../connection_adapters/postgresql_adapter.rb | 74 ++++------------------ 1 file changed, 12 insertions(+), 62 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 5a830a50fb..acd4c5cc4a 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -229,6 +229,11 @@ module ActiveRecord @statements = {} connect + + if postgresql_version < 80200 + raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!" + end + @local_tz = execute('SHOW TIME ZONE').first["TimeZone"] end @@ -299,7 +304,7 @@ module ActiveRecord end def supports_insert_with_returning? - postgresql_version >= 80200 + true end def supports_ddl_transactions? @@ -310,10 +315,9 @@ module ActiveRecord true end - # Returns the configured supported identifier length supported by PostgreSQL, - # or report the default of 63 on PostgreSQL 7.x. + # Returns the configured supported identifier length supported by PostgreSQL def table_alias_length - @table_alias_length ||= (postgresql_version >= 80000 ? query('SHOW max_identifier_length')[0][0].to_i : 63) + @table_alias_length ||= query('SHOW max_identifier_length')[0][0].to_i end # QUOTING ================================================== @@ -404,7 +408,7 @@ module ActiveRecord # REFERENTIAL INTEGRITY ==================================== def supports_disable_referential_integrity?() #:nodoc: - postgresql_version >= 80100 + true end def disable_referential_integrity #:nodoc: @@ -632,15 +636,7 @@ module ActiveRecord # Example: # drop_database 'matt_development' def drop_database(name) #:nodoc: - if postgresql_version >= 80200 - execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}" - else - begin - execute "DROP DATABASE #{quote_table_name(name)}" - rescue ActiveRecord::StatementInvalid - @logger.warn "#{name} database doesn't exist." if @logger - end - end + execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}" end # Returns the list of all tables in the schema search path or a specified schema. @@ -803,28 +799,6 @@ module ActiveRecord AND dep.refobjid = '#{quote_table_name(table)}'::regclass end_sql - if result.nil? or result.empty? - # If that fails, try parsing the primary key's default value. - # Support the 7.x and 8.0 nextval('foo'::text) as well as - # the 8.1+ nextval('foo'::regclass). - result = query(<<-end_sql, 'PK and custom sequence')[0] - SELECT attr.attname, - CASE - WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN - substr(split_part(def.adsrc, '''', 2), - strpos(split_part(def.adsrc, '''', 2), '.')+1) - ELSE split_part(def.adsrc, '''', 2) - END - FROM pg_class t - JOIN pg_attribute attr ON (t.oid = attrelid) - JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum) - JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1]) - WHERE t.oid = '#{quote_table_name(table)}'::regclass - AND cons.contype = 'p' - AND def.adsrc ~* 'nextval' - end_sql - end - # [primary_key, sequence] [result.first, result.last] rescue @@ -848,38 +822,14 @@ module ActiveRecord add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" add_column_options!(add_column_sql, options) - begin - execute add_column_sql - rescue ActiveRecord::StatementInvalid => e - raise e if postgresql_version > 80000 - - execute("ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}") - change_column_default(table_name, column_name, options[:default]) if options_include_default?(options) - change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null) - end + execute add_column_sql end # Changes the column of a table. def change_column(table_name, column_name, type, options = {}) quoted_table_name = quote_table_name(table_name) - begin - execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" - rescue ActiveRecord::StatementInvalid => e - raise e if postgresql_version > 80000 - # This is PostgreSQL 7.x, so we have to use a more arcane way of doing it. - begin - begin_db_transaction - tmp_column_name = "#{column_name}_ar_tmp" - add_column(table_name, tmp_column_name, type, options) - execute "UPDATE #{quoted_table_name} SET #{quote_column_name(tmp_column_name)} = CAST(#{quote_column_name(column_name)} AS #{type_to_sql(type, options[:limit], options[:precision], options[:scale])})" - remove_column(table_name, column_name) - rename_column(table_name, tmp_column_name, column_name) - commit_db_transaction - rescue - rollback_db_transaction - end - end + execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" change_column_default(table_name, column_name, options[:default]) if options_include_default?(options) change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null) -- cgit v1.2.3 From 1f3d3eb49d16b62250c24e3374cc36de99b397b8 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sun, 10 Apr 2011 18:02:57 -0700 Subject: pg should define insert_sql so that query cache actually works for inserts --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index acd4c5cc4a..7546afcc18 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -431,7 +431,7 @@ module ActiveRecord end # Executes an INSERT query and returns the new record's ID - def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) + def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) # Extract the table from the insert sql. Yuck. table = sql.split(" ", 4)[2].gsub('"', '') @@ -440,7 +440,6 @@ module ActiveRecord pk, sequence_name = *pk_and_sequence_for(table) unless pk if pk id = select_value("#{sql} RETURNING #{quote_column_name(pk)}") - clear_query_cache return id end end -- cgit v1.2.3 From 35b2715456999662cc34390e258962738aaa8dc7 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sun, 10 Apr 2011 23:53:02 -0700 Subject: only support pg >= 8.2, so no need to check --- .../active_record/connection_adapters/postgresql_adapter.rb | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 7546afcc18..15ced08499 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -435,13 +435,10 @@ module ActiveRecord # Extract the table from the insert sql. Yuck. table = sql.split(" ", 4)[2].gsub('"', '') - # Try an insert with 'returning id' if available (PG >= 8.2) - if supports_insert_with_returning? - pk, sequence_name = *pk_and_sequence_for(table) unless pk - if pk - id = select_value("#{sql} RETURNING #{quote_column_name(pk)}") - return id - end + pk, sequence_name = *pk_and_sequence_for(table) unless pk + if pk + id = select_value("#{sql} RETURNING #{quote_column_name(pk)}") + return id end # Otherwise, insert then grab last_insert_id. -- cgit v1.2.3 From c4fc3963003f53cb963435ea3733bd3e8164c803 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 11 Apr 2011 10:02:39 -0700 Subject: adding a case_sensitive_modifier for forcing comparisons to be case sensitive --- .../lib/active_record/connection_adapters/abstract_adapter.rb | 4 ++++ activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb | 5 +++++ activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 5 +++++ activerecord/lib/active_record/validations/uniqueness.rb | 1 + 4 files changed, 15 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 0f44baa2fe..b0e6136e12 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -203,6 +203,10 @@ module ActiveRecord def release_savepoint end + def case_sensitive_modifier(node) + node + end + def current_savepoint_name "active_record_#{open_transactions}" end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 7bad511c64..7e237db357 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -528,6 +528,11 @@ module ActiveRecord def case_sensitive_equality_operator "= BINARY" end + deprecate :case_sensitive_equality_operator + + def case_sensitive_modifier(node) + Arel::Nodes::Bin.new(node) + end def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key) where_sql diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index e1186209d3..ceee2ab73a 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -655,6 +655,11 @@ module ActiveRecord def case_sensitive_equality_operator "= BINARY" end + deprecate :case_sensitive_equality_operator + + def case_sensitive_modifier(node) + Arel::Nodes::Bin.new(node) + end def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key) where_sql diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 37381d063d..d1225a9ed9 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -59,6 +59,7 @@ module ActiveRecord if !options[:case_sensitive] && column.text? relation = table[attribute].matches(value) else + value = klass.connection.case_sensitive_modifier(value) relation = table[attribute].eq(value) end -- cgit v1.2.3 From 32dbf00d99c28b0ce143ceb5f94abcd93019eb61 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 8 Apr 2011 14:50:42 -0700 Subject: adding exec_insert to sqlite3 --- activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 32229a8410..5c4d76be98 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -173,6 +173,10 @@ module ActiveRecord end end + def exec_insert(sql, name, binds) + exec_query(sql, name, binds) + end + def execute(sql, name = nil) #:nodoc: log(sql, name) { @connection.execute(sql) } end -- cgit v1.2.3 From f9d3f018669bc96b107cfa802d2fa9b0b5555717 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 8 Apr 2011 16:44:54 -0700 Subject: properly name schema queries for logging --- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (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 ceee2ab73a..33add72283 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -506,7 +506,7 @@ module ActiveRecord def tables(name = nil, database = nil) #:nodoc: tables = [] - result = execute(["SHOW TABLES", database].compact.join(' IN '), name) + result = execute(["SHOW TABLES", database].compact.join(' IN '), 'SCHEMA') result.each { |field| tables << field[0] } result.free tables @@ -551,7 +551,7 @@ module ActiveRecord def columns(table_name, name = nil)#:nodoc: sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}" columns = [] - result = execute(sql) + result = execute(sql, 'SCHEMA') result.each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") } result.free columns @@ -638,7 +638,7 @@ module ActiveRecord # Returns a table's primary key and belonging sequence. def pk_and_sequence_for(table) #:nodoc: keys = [] - result = execute("describe #{quote_table_name(table)}") + result = execute("describe #{quote_table_name(table)}", 'SCHEMA') result.each_hash do |h| keys << h["Field"]if h["Key"] == "PRI" end -- cgit v1.2.3 From 0845b5062d118197f2d1531dfbb9dd9ddd90bef2 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 8 Apr 2011 17:37:10 -0700 Subject: adding client_encoding method for discovering the encoding set for this client, testing exec_insert on a string --- .../connection_adapters/mysql_adapter.rb | 62 ++++++++++++++++++++++ 1 file changed, 62 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 33add72283..d88075fdea 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -196,6 +196,7 @@ module ActiveRecord @connection_options, @config = connection_options, config @quoted_column_names, @quoted_table_names = {}, {} @statements = {} + @client_encoding = nil connect end @@ -330,6 +331,63 @@ module ActiveRecord @statements.clear end + if "<3".respond_to?(:encode) + # Taken from here: + # https://github.com/tmtm/ruby-mysql/blob/master/lib/mysql/charset.rb + # Author: TOMITA Masahiro + ENCODINGS = { + "armscii8" => nil, + "ascii" => Encoding::US_ASCII, + "big5" => Encoding::Big5, + "binary" => Encoding::ASCII_8BIT, + "cp1250" => Encoding::Windows_1250, + "cp1251" => Encoding::Windows_1251, + "cp1256" => Encoding::Windows_1256, + "cp1257" => Encoding::Windows_1257, + "cp850" => Encoding::CP850, + "cp852" => Encoding::CP852, + "cp866" => Encoding::IBM866, + "cp932" => Encoding::Windows_31J, + "dec8" => nil, + "eucjpms" => Encoding::EucJP_ms, + "euckr" => Encoding::EUC_KR, + "gb2312" => Encoding::EUC_CN, + "gbk" => Encoding::GBK, + "geostd8" => nil, + "greek" => Encoding::ISO_8859_7, + "hebrew" => Encoding::ISO_8859_8, + "hp8" => nil, + "keybcs2" => nil, + "koi8r" => Encoding::KOI8_R, + "koi8u" => Encoding::KOI8_U, + "latin1" => Encoding::ISO_8859_1, + "latin2" => Encoding::ISO_8859_2, + "latin5" => Encoding::ISO_8859_9, + "latin7" => Encoding::ISO_8859_13, + "macce" => Encoding::MacCentEuro, + "macroman" => Encoding::MacRoman, + "sjis" => Encoding::SHIFT_JIS, + "swe7" => nil, + "tis620" => Encoding::TIS_620, + "ucs2" => Encoding::UTF_16BE, + "ujis" => Encoding::EucJP_ms, + "utf8" => Encoding::UTF_8, + "utf8mb4" => Encoding::UTF_8, + } + else + ENCODINGS = Hash.new { |h,k| h[k] = k } + end + + # Get the client encoding for this database + def client_encoding + return @client_encoding if @client_encoding + + result = exec_query( + "SHOW VARIABLES WHERE Variable_name = 'character_set_client'", + 'SCHEMA') + @client_encoding = ENCODINGS[result.rows.last.last] + end + def exec_query(sql, name = 'SQL', binds = []) log(sql, name, binds) do result = nil @@ -363,6 +421,10 @@ module ActiveRecord end end + def exec_insert(sql, name, binds) + exec_query(sql, name, binds) + end + def exec_without_stmt(sql, name = 'SQL') # :nodoc: # Some queries, like SHOW CREATE TABLE don't work through the prepared # statement API. For those queries, we need to use this method. :'( -- cgit v1.2.3 From 90a371496a7a44b74879011d4e316ffaa6fcf299 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 8 Apr 2011 17:45:18 -0700 Subject: properly name schema queries for the logger --- .../connection_adapters/postgresql_adapter.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 15ced08499..08329b33ed 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -234,7 +234,7 @@ module ActiveRecord raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!" end - @local_tz = execute('SHOW TIME ZONE').first["TimeZone"] + @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"] end def clear_cache! @@ -298,7 +298,7 @@ module ActiveRecord # Enable standard-conforming strings if available. def set_standard_conforming_strings old, self.client_min_messages = client_min_messages, 'panic' - execute('SET standard_conforming_strings = on') rescue nil + execute('SET standard_conforming_strings = on', 'SCHEMA') rescue nil ensure self.client_min_messages = old end @@ -637,7 +637,7 @@ module ActiveRecord # Returns the list of all tables in the schema search path or a specified schema. def tables(name = nil) - query(<<-SQL, name).map { |row| row[0] } + query(<<-SQL, 'SCHEMA').map { |row| row[0] } SELECT tablename FROM pg_tables WHERE schemaname = ANY (current_schemas(false)) @@ -658,7 +658,7 @@ module ActiveRecord schema = nil end - query(<<-SQL).first[0].to_i > 0 + query(<<-SQL, 'SCHEMA').first[0].to_i > 0 SELECT COUNT(*) FROM pg_tables WHERE tablename = '#{table.gsub(/(^"|"$)/,'')}' @@ -740,12 +740,12 @@ module ActiveRecord # Returns the current client message level. def client_min_messages - query('SHOW client_min_messages')[0][0] + query('SHOW client_min_messages', 'SCHEMA')[0][0] end # Set the client message level. def client_min_messages=(level) - execute("SET client_min_messages TO '#{level}'") + execute("SET client_min_messages TO '#{level}'", 'SCHEMA') end # Returns the sequence name for a table's primary key or some other specified key. @@ -778,7 +778,7 @@ module ActiveRecord def pk_and_sequence_for(table) #:nodoc: # First try looking for a sequence with a dependency on the # given table's primary key. - result = exec_query(<<-end_sql, 'PK and serial sequence').rows.first + result = exec_query(<<-end_sql, 'SCHEMA').rows.first SELECT attr.attname, seq.relname FROM pg_class seq, pg_attribute attr, @@ -977,9 +977,9 @@ module ActiveRecord # If using Active Record's time zone support configure the connection to return # TIMESTAMP WITH ZONE types in UTC. if ActiveRecord::Base.default_timezone == :utc - execute("SET time zone 'UTC'") + execute("SET time zone 'UTC'", 'SCHEMA') elsif @local_tz - execute("SET time zone '#{@local_tz}'") + execute("SET time zone '#{@local_tz}'", 'SCHEMA') end end @@ -1022,7 +1022,7 @@ module ActiveRecord # - format_type includes the column size constraint, e.g. varchar(50) # - ::regclass is a function that gives the id for a table name def column_definitions(table_name) #:nodoc: - exec_query(<<-end_sql).rows + exec_query(<<-end_sql, 'SCHEMA').rows SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull FROM pg_attribute a LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum -- cgit v1.2.3 From 58259bbf29b55571da7708607463e61a9b69565d Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 8 Apr 2011 18:19:24 -0700 Subject: adding exec_insert for postgresql --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 08329b33ed..a0be143c8e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -554,6 +554,10 @@ module ActiveRecord end end + def exec_insert(sql, name, binds) + exec_query(sql, name, binds) + end + # Executes an UPDATE query and returns the number of affected tuples. def update_sql(sql, name = nil) super.cmd_tuples -- cgit v1.2.3 From 8a11799a474d6cc1dc599e8823ce5bbb012944e7 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sun, 10 Apr 2011 18:12:42 -0700 Subject: make sqlite insert_sql more consistent with other adapters --- activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 5c4d76be98..8ef286b473 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -192,7 +192,8 @@ module ActiveRecord end def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: - super || @connection.last_insert_row_id + super + id_value || @connection.last_insert_row_id end alias :create :insert_sql -- cgit v1.2.3 From 269cd1b3c55c6ceb69c167ad97c357b56f49cb4b Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sun, 10 Apr 2011 19:02:06 -0700 Subject: implement exec_query on mysql2 adapter --- .../connection_adapters/mysql2_adapter.rb | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 7e237db357..1f7e527eeb 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -592,8 +592,26 @@ module ActiveRecord # Returns an array of record hashes with the column names as keys and # column values as values. - def select(sql, name = nil) - execute(sql, name).each(:as => :hash) + def select(sql, name = nil, binds = []) + exec_query(sql, name, binds).to_a + end + + def exec_query(sql, name = 'SQL', binds = []) + @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone + + log(sql, name, binds) do + begin + result = @connection.query(sql) + rescue ActiveRecord::StatementInvalid => exception + if exception.message.split(":").first =~ /Packets out of order/ + raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings." + else + raise + end + end + + ActiveRecord::Result.new(result.fields, result.to_a) + end end def supports_views? -- cgit v1.2.3 From 4c30304e159b2a8fafb5b3c5c8b6d996e490e657 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sun, 10 Apr 2011 19:08:20 -0700 Subject: updating the docco for ActiveRecord::Result --- activerecord/lib/active_record/result.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb index 0465b21e88..243012f88c 100644 --- a/activerecord/lib/active_record/result.rb +++ b/activerecord/lib/active_record/result.rb @@ -1,9 +1,9 @@ module ActiveRecord ### - # This class encapsulates a Result returned from calling +exec+ on any + # This class encapsulates a Result returned from calling +exec_query+ on any # database connection adapter. For example: # - # x = ActiveRecord::Base.connection.exec('SELECT * FROM foo') + # x = ActiveRecord::Base.connection.exec_query('SELECT * FROM foo') # x # => # class Result include Enumerable -- cgit v1.2.3 From 302b6f3f73731c8daa108f9c40d7cf41463898d8 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 11 Apr 2011 10:31:13 -0700 Subject: pg does not know the insert_id in advance, so super will never return true --- .../connection_adapters/postgresql_adapter.rb | 23 ++++++++++------------ 1 file changed, 10 insertions(+), 13 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index a0be143c8e..a90e9a4b71 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -441,20 +441,17 @@ module ActiveRecord return id end - # Otherwise, insert then grab last_insert_id. - if insert_id = super - insert_id - else - # If neither pk nor sequence name is given, look them up. - unless pk || sequence_name - pk, sequence_name = *pk_and_sequence_for(table) - end + super - # If a pk is given, fallback to default sequence name. - # Don't fetch last insert id for a table without a pk. - if pk && sequence_name ||= default_sequence_name(table, pk) - last_insert_id(sequence_name) - end + # If neither pk nor sequence name is given, look them up. + unless pk || sequence_name + pk, sequence_name = *pk_and_sequence_for(table) + end + + # If a pk is given, fallback to default sequence name. + # Don't fetch last insert id for a table without a pk. + if pk && sequence_name ||= default_sequence_name(table, pk) + last_insert_id(sequence_name) end end alias :create :insert -- cgit v1.2.3 From cbb65de1a60f1263cab05627ab99c9f5e246cd5c Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 11 Apr 2011 10:37:23 -0700 Subject: always look up pk and sequence unless both are provided --- .../active_record/connection_adapters/postgresql_adapter.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index a90e9a4b71..617d131c40 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -435,7 +435,11 @@ module ActiveRecord # Extract the table from the insert sql. Yuck. table = sql.split(" ", 4)[2].gsub('"', '') - pk, sequence_name = *pk_and_sequence_for(table) unless pk + # If neither pk nor sequence name is given, look them up. + unless pk || sequence_name + pk, sequence_name = *pk_and_sequence_for(table) + end + if pk id = select_value("#{sql} RETURNING #{quote_column_name(pk)}") return id @@ -443,11 +447,6 @@ module ActiveRecord super - # If neither pk nor sequence name is given, look them up. - unless pk || sequence_name - pk, sequence_name = *pk_and_sequence_for(table) - end - # If a pk is given, fallback to default sequence name. # Don't fetch last insert id for a table without a pk. if pk && sequence_name ||= default_sequence_name(table, pk) -- cgit v1.2.3 From 9ba94c8fc4e7d5577e87aca07f92c9f51f08bdba Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 11 Apr 2011 10:54:08 -0700 Subject: we know the table and pk, so we can calculate a default sequence name --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 617d131c40..392eadb9d5 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -447,11 +447,12 @@ module ActiveRecord super + return unless pk + # If a pk is given, fallback to default sequence name. # Don't fetch last insert id for a table without a pk. - if pk && sequence_name ||= default_sequence_name(table, pk) - last_insert_id(sequence_name) - end + sequence_name ||= "#{table}_#{pk}_seq" + last_insert_id(sequence_name) end alias :create :insert -- cgit v1.2.3 From 5df072d64bf24213c1eb99b3762b4fd597e28903 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 11 Apr 2011 13:10:20 -0700 Subject: last insert id can never be called, so remove that code path --- .../connection_adapters/postgresql_adapter.rb | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 392eadb9d5..c179be828e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -441,18 +441,10 @@ module ActiveRecord end if pk - id = select_value("#{sql} RETURNING #{quote_column_name(pk)}") - return id + select_value("#{sql} RETURNING #{quote_column_name(pk)}") + else + super end - - super - - return unless pk - - # If a pk is given, fallback to default sequence name. - # Don't fetch last insert id for a table without a pk. - sequence_name ||= "#{table}_#{pk}_seq" - last_insert_id(sequence_name) end alias :create :insert -- cgit v1.2.3 From a9e8554b46a29573094eec139b16778e29e5c37a Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 11 Apr 2011 13:57:00 -0700 Subject: use prepared statements for primary key queries --- .../connection_adapters/postgresql_adapter.rb | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index c179be828e..468f8d75d7 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -796,8 +796,21 @@ module ActiveRecord # Returns just a table's primary key def primary_key(table) - pk_and_sequence = pk_and_sequence_for(table) - pk_and_sequence && pk_and_sequence.first + row = exec_query(<<-end_sql, 'SCHEMA', [[nil, table]]).rows.first + SELECT DISTINCT(attr.attname) + FROM pg_attribute attr, + pg_depend dep, + pg_namespace name, + pg_constraint cons + WHERE attr.attrelid = dep.refobjid + AND attr.attnum = dep.refobjsubid + AND attr.attrelid = cons.conrelid + AND attr.attnum = cons.conkey[1] + AND cons.contype = 'p' + AND dep.refobjid = $1::regclass + end_sql + + row && row.first end # Renames a table. -- cgit v1.2.3 From f6c0c8ff6101fed24dd176fb0cce0383807c502d Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 11 Apr 2011 14:08:27 -0700 Subject: only use the primary_key method, refactor schema and table name parsing --- .../connection_adapters/postgresql_adapter.rb | 28 ++++++++++++---------- 1 file changed, 15 insertions(+), 13 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 468f8d75d7..d76b03e80a 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -433,12 +433,9 @@ module ActiveRecord # Executes an INSERT query and returns the new record's ID def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) # Extract the table from the insert sql. Yuck. - table = sql.split(" ", 4)[2].gsub('"', '') + _, table = extract_schema_and_table(sql.split(" ", 4)[2]) - # If neither pk nor sequence name is given, look them up. - unless pk || sequence_name - pk, sequence_name = *pk_and_sequence_for(table) - end + pk ||= primary_key(table) if pk select_value("#{sql} RETURNING #{quote_column_name(pk)}") @@ -638,7 +635,18 @@ module ActiveRecord end def table_exists?(name) - name = name.to_s + schema, table = extract_schema_and_table(name.to_s) + + query(<<-SQL, 'SCHEMA').first[0].to_i > 0 + SELECT COUNT(*) + FROM pg_tables + WHERE tablename = '#{table.gsub(/(^"|"$)/,'')}' + #{schema ? "AND schemaname = '#{schema}'" : ''} + SQL + end + + # Extracts the table and schema name from +name+ + def extract_schema_and_table(name) schema, table = name.split('.', 2) unless table # A table was provided without a schema @@ -650,13 +658,7 @@ module ActiveRecord table = name schema = nil end - - query(<<-SQL, 'SCHEMA').first[0].to_i > 0 - SELECT COUNT(*) - FROM pg_tables - WHERE tablename = '#{table.gsub(/(^"|"$)/,'')}' - #{schema ? "AND schemaname = '#{schema}'" : ''} - SQL + [schema, table] end # Returns the list of all indexes for a table. -- cgit v1.2.3 From 75dc9fbac76a2da78b8d21e1ede16fea38d16564 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 11 Apr 2011 14:12:31 -0700 Subject: cache table exists queries in prepared statement cache --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index d76b03e80a..015d5eb646 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -637,11 +637,14 @@ module ActiveRecord def table_exists?(name) schema, table = extract_schema_and_table(name.to_s) - query(<<-SQL, 'SCHEMA').first[0].to_i > 0 + binds = [[nil, table.gsub(/(^"|"$)/,'')]] + binds << [nil, schema] if schema + + exec_query(<<-SQL, 'SCHEMA', binds).rows.first[0].to_i > 0 SELECT COUNT(*) FROM pg_tables - WHERE tablename = '#{table.gsub(/(^"|"$)/,'')}' - #{schema ? "AND schemaname = '#{schema}'" : ''} + WHERE tablename = $1 + #{schema ? "AND schemaname = $2" : ''} SQL end -- cgit v1.2.3 From 622f23b604546784aeab3cca5a47f0d6b281cf91 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 11 Apr 2011 14:38:42 -0700 Subject: wrap the pg_get_serial_sequence function and reuse it for the default sequence name --- .../active_record/connection_adapters/postgresql_adapter.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 015d5eb646..2283a5bb68 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -748,8 +748,16 @@ module ActiveRecord # Returns the sequence name for a table's primary key or some other specified key. def default_sequence_name(table_name, pk = nil) #:nodoc: - default_pk, default_seq = pk_and_sequence_for(table_name) - default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq" + serial_sequence(table_name, pk || 'id').split('.').last + rescue ActiveRecord::StatementInvalid + "#{table_name}_#{pk || 'id'}_seq" + end + + def serial_sequence(table, column) + result = exec_query(<<-eosql, 'SCHEMA', [[nil, table], [nil, column]]) + SELECT pg_get_serial_sequence($1, $2) + eosql + result.rows.first.first end # Resets the sequence of a table's primary key to the maximum value. -- cgit v1.2.3 From 5918b868b24ff384365d436367611aea577f3723 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 11 Apr 2011 15:19:10 -0700 Subject: remove so many nested if statements --- .../connection_adapters/postgresql_adapter.rb | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 2283a5bb68..1c2cc7d5fa 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -764,19 +764,21 @@ module ActiveRecord def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc: unless pk and sequence default_pk, default_sequence = pk_and_sequence_for(table) + pk ||= default_pk sequence ||= default_sequence end - if pk - if sequence - quoted_sequence = quote_column_name(sequence) - select_value <<-end_sql, 'Reset sequence' - SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false) - end_sql - else - @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger - end + if @logger && pk && !sequence + @logger.warn "#{table} has primary key #{pk} with no default sequence" + end + + if pk && sequence + quoted_sequence = quote_column_name(sequence) + + select_value <<-end_sql, 'Reset sequence' + SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false) + end_sql end end -- cgit v1.2.3 From d1575ae1b9658c91145d6a46ec2a144a5a089207 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 12 Apr 2011 00:23:07 +0200 Subject: Change Object#either? to Object#among? -- thanks to @jamesarosen for the suggestion! --- activerecord/lib/active_record/associations/association.rb | 2 +- activerecord/lib/active_record/associations/builder/belongs_to.rb | 2 +- activerecord/lib/active_record/associations/builder/has_many.rb | 2 +- activerecord/lib/active_record/associations/builder/has_one.rb | 2 +- activerecord/lib/active_record/associations/has_one_association.rb | 2 +- .../lib/active_record/attribute_methods/time_zone_conversion.rb | 2 +- activerecord/lib/active_record/railties/databases.rake | 2 +- activerecord/lib/active_record/reflection.rb | 2 +- .../active_record/session_migration/session_migration_generator.rb | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index b250783d9e..66f6477ec5 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -164,7 +164,7 @@ module ActiveRecord def creation_attributes attributes = {} - if reflection.macro.either?(:has_one, :has_many) && !options[:through] + if reflection.macro.among?(:has_one, :has_many) && !options[:through] attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key] if reflection.options[:as] diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 9ea37da78f..763b12f708 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -67,7 +67,7 @@ module ActiveRecord::Associations::Builder def configure_dependency if options[:dependent] - unless options[:dependent].either?(:destroy, :delete) + unless options[:dependent].among?(:destroy, :delete) raise ArgumentError, "The :dependent option expects either :destroy or :delete (#{options[:dependent].inspect})" end diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb index 3b489d9647..67ce339380 100644 --- a/activerecord/lib/active_record/associations/builder/has_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_many.rb @@ -16,7 +16,7 @@ module ActiveRecord::Associations::Builder def configure_dependency if options[:dependent] - unless options[:dependent].either?(:destroy, :delete_all, :nullify, :restrict) + unless options[:dependent].among?(:destroy, :delete_all, :nullify, :restrict) raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, " \ ":nullify or :restrict (#{options[:dependent].inspect})" end diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb index 7efe2ec74d..18d7117bb7 100644 --- a/activerecord/lib/active_record/associations/builder/has_one.rb +++ b/activerecord/lib/active_record/associations/builder/has_one.rb @@ -29,7 +29,7 @@ module ActiveRecord::Associations::Builder def configure_dependency if options[:dependent] - unless options[:dependent].either?(:destroy, :delete, :nullify, :restrict) + unless options[:dependent].among?(:destroy, :delete, :nullify, :restrict) raise ArgumentError, "The :dependent option expects either :destroy, :delete, " \ ":nullify or :restrict (#{options[:dependent].inspect})" end diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 82f5696ad8..aaa8b97389 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -52,7 +52,7 @@ module ActiveRecord end def remove_target!(method) - if method.either?(:delete, :destroy) + if method.among?(:delete, :destroy) target.send(method) else nullify_owner_attributes(target) diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index 032a3135f0..67b0e77a25 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -59,7 +59,7 @@ module ActiveRecord private def create_time_zone_conversion_attribute?(name, column) - time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) && column.type.either?(:datetime, :timestamp) + time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) && column.type.among?(:datetime, :timestamp) end end end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index a1b0270d1b..944a29a3e6 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -137,7 +137,7 @@ db_namespace = namespace :db do end def local_database?(config, &block) - if config['host'].either?("127.0.0.1", "localhost") || config['host'].blank? + if config['host'].among?("127.0.0.1", "localhost") || config['host'].blank? yield else $stderr.puts "This task only modifies local databases. #{config['database']} is on a remote host." diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 82561e86cf..eac6a5dcad 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -164,7 +164,7 @@ module ActiveRecord def initialize(macro, name, options, active_record) super - @collection = macro.either?(:has_many, :has_and_belongs_to_many) + @collection = macro.among?(:has_many, :has_and_belongs_to_many) end # Returns a new, unsaved instance of the associated class. +options+ will diff --git a/activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb b/activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb index e803ee5764..d80c8ba996 100644 --- a/activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +++ b/activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb @@ -14,7 +14,7 @@ module ActiveRecord def session_table_name current_table_name = ActiveRecord::SessionStore::Session.table_name - if current_table_name.either?("sessions", "session") + if current_table_name.among?("sessions", "session") current_table_name = (ActiveRecord::Base.pluralize_table_names ? 'session'.pluralize : 'session') end current_table_name -- cgit v1.2.3 From b53ffb35e085362bb2690577ae123a1f4872c12a Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 11 Apr 2011 15:49:37 -0700 Subject: stop using deprecated methods in arel --- activerecord/lib/active_record/relation/finder_methods.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 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 7fe6fe0ed0..673e47942b 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -352,8 +352,8 @@ module ActiveRecord if result.size == expected_size result else - conditions = arel.wheres.map { |x| x.value }.join(', ') - conditions = " [WHERE #{conditions}]" if conditions.present? + conditions = arel.where_sql + conditions = " [#{conditions}]" if conditions error = "Couldn't find all #{@klass.name.pluralize} with IDs " error << "(#{ids.join(", ")})#{conditions} (found #{result.size} results, but was looking for #{expected_size})" -- cgit v1.2.3 From fc9a04b6a69d6821a6f1601e3e0dd518300fa138 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 28 Mar 2011 22:36:29 +0100 Subject: Removing the scope-caching which happens on association proxies, because the query is already cached by the query cacher. For formalised proof see http://www.youtube.com/watch?v=wDefXLb-FDs --- .../lib/active_record/associations/collection_association.rb | 12 ------------ .../lib/active_record/associations/collection_proxy.rb | 2 -- 2 files changed, 14 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 9f4fc44cc6..33a184d48d 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -21,14 +21,7 @@ module ActiveRecord attr_reader :proxy def initialize(owner, reflection) - # When scopes are created via method_missing on the proxy, they are stored so that - # any records fetched from the database are kept around for future use. - @scopes_cache = Hash.new do |hash, method| - hash[method] = { } - end - super - @proxy = CollectionProxy.new(self) end @@ -74,7 +67,6 @@ module ActiveRecord def reset @loaded = false @target = [] - @scopes_cache.clear end def select(select = nil) @@ -327,10 +319,6 @@ module ActiveRecord end end - def cached_scope(method, args) - @scopes_cache[method][args] ||= scoped.readonly(nil).send(method, *args) - end - def load_target if find_target? targets = [] diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index cf77d770c9..388173c1fb 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -82,8 +82,6 @@ module ActiveRecord end end - elsif @association.klass.scopes[method] - @association.cached_scope(method, args) else scoped.readonly(nil).send(method, *args, &block) end -- cgit v1.2.3 From 5740d4ec0c16d68b82f440e74fd8b74ae3fe48e6 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 4 Apr 2011 00:07:45 +0100 Subject: Deprecated support for passing hashes and relations to default_scope, in favour of defining a 'default_scope' class method in the model. See the CHANGELOG for more details. --- activerecord/lib/active_record/base.rb | 144 +++++++++++++++++--------- activerecord/lib/active_record/named_scope.rb | 2 +- activerecord/lib/active_record/relation.rb | 7 +- 3 files changed, 95 insertions(+), 58 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index fe81c7dc2f..e0fb94c96e 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -425,8 +425,8 @@ module ActiveRecord #:nodoc: self.store_full_sti_class = true # Stores the default scope for the class - class_attribute :default_scoping, :instance_writer => false - self.default_scoping = [] + class_attribute :default_scopes, :instance_writer => false + self.default_scopes = [] # Returns a hash of all the attributes that have been specified for serialization as # keys and their class restriction as values. @@ -870,7 +870,9 @@ module ActiveRecord #:nodoc: # Returns a scope for this class without taking into account the default_scope. # # class Post < ActiveRecord::Base - # default_scope :published => true + # def self.default_scope + # where :published => true + # end # end # # Post.all # Fires "SELECT * FROM posts WHERE published = true" @@ -892,13 +894,8 @@ module ActiveRecord #:nodoc: block_given? ? relation.scoping { yield } : relation end - def scoped_methods #:nodoc: - key = :"#{self}_scoped_methods" - Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup - end - def before_remove_const #:nodoc: - reset_scoped_methods + self.current_scope = nil end # Specifies how the record is loaded by +Marshal+. @@ -1020,7 +1017,7 @@ module ActiveRecord #:nodoc: super unless all_attributes_exists?(attribute_names) if match.finder? options = arguments.extract_options! - relation = options.any? ? construct_finder_arel(options, current_scoped_methods) : scoped + relation = options.any? ? scoped(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 @@ -1109,43 +1106,48 @@ module ActiveRecord #:nodoc: # end # # *Note*: the +:find+ scope also has effect on update and deletion methods, like +update_all+ and +delete_all+. - def with_scope(method_scoping = {}, action = :merge, &block) - method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping) + def with_scope(scope = {}, action = :merge, &block) + # If another Active Record class has been passed in, get its current scope + scope = scope.current_scope if !scope.is_a?(Relation) && scope.respond_to?(:current_scope) + + # Cannot use self.current_scope, because that could trigger build_default_scope, which + # could in turn result in with_scope being called and hence an infinite loop. + previous_scope = Thread.current[:"#{self}_current_scope"] - if method_scoping.is_a?(Hash) + if scope.is_a?(Hash) # Dup first and second level of hash (method and params). - method_scoping = method_scoping.dup - method_scoping.each do |method, params| - method_scoping[method] = params.dup unless params == true + scope = scope.dup + scope.each do |method, params| + scope[method] = params.dup unless params == true end - method_scoping.assert_valid_keys([ :find, :create ]) - relation = construct_finder_arel(method_scoping[:find] || {}) + scope.assert_valid_keys([ :find, :create ]) + relation = construct_finder_arel(scope[:find] || {}) - if current_scoped_methods && current_scoped_methods.create_with_value && method_scoping[:create] + if previous_scope && previous_scope.create_with_value && scope[:create] scope_for_create = if action == :merge - current_scoped_methods.create_with_value.merge(method_scoping[:create]) + previous_scope.create_with_value.merge(scope[:create]) else - method_scoping[:create] + scope[:create] end relation = relation.create_with(scope_for_create) else - scope_for_create = method_scoping[:create] - scope_for_create ||= current_scoped_methods.create_with_value if current_scoped_methods + scope_for_create = scope[:create] + scope_for_create ||= previous_scope.create_with_value if previous_scope relation = relation.create_with(scope_for_create) if scope_for_create end - method_scoping = relation + scope = relation end - method_scoping = current_scoped_methods.merge(method_scoping) if current_scoped_methods && action == :merge + scope = previous_scope.merge(scope) if previous_scope && action == :merge - self.scoped_methods << method_scoping + self.current_scope = scope begin yield ensure - self.scoped_methods.pop + self.current_scope = previous_scope end end @@ -1168,39 +1170,82 @@ MSG with_scope(method_scoping, :overwrite, &block) end - # Sets the default options for the model. The format of the - # options argument is the same as in find. + def current_scope #:nodoc: + Thread.current[:"#{self}_current_scope"] ||= build_default_scope + end + + def current_scope=(scope) #:nodoc: + Thread.current[:"#{self}_current_scope"] = scope + end + + # Implement this method in your model to set a default scope for all operations on + # the model. # # class Person < ActiveRecord::Base - # default_scope order('last_name, first_name') + # def self.default_scope + # order('last_name, first_name') + # end # end # - # default_scope is also applied while creating/building a record. It is not + # Person.all # => SELECT * FROM people ORDER BY last_name, first_name + # + # The default_scope is also applied while creating/building a record. It is not # applied while updating a record. # # class Article < ActiveRecord::Base - # default_scope where(:published => true) + # def self.default_scope + # where(:published => true) + # end # end # # Article.new.published # => true # Article.create.published # => true - def default_scope(options = {}) - reset_scoped_methods - default_scoping = self.default_scoping.dup - self.default_scoping = default_scoping << construct_finder_arel(options, default_scoping.pop) - end + # + # === Deprecation warning + # + # There is an alternative syntax as follows: + # + # class Person < ActiveRecord::Base + # default_scope order('last_name, first_name') + # end + # + # This is now deprecated and will be removed in Rails 3.2. + def default_scope(scope = {}) + ActiveSupport::Deprecation.warn <<-WARN +Passing a hash or scope to default_scope is deprecated and will be removed in Rails 3.2. You should create a class method for your scope instead. For example, change this: - def current_scoped_methods #:nodoc: - method = scoped_methods.last - if method.respond_to?(:call) - relation.scoping { method.call } - else - method - end +class Post < ActiveRecord::Base + default_scope where(:published => true) +end + +To this: + +class Post < ActiveRecord::Base + def self.default_scope + where(:published => true) + end +end +WARN + + # Reset the current scope as it may contain scopes based on a now-invalid default scope + self.current_scope = nil + self.default_scopes = default_scopes.dup << scope end - def reset_scoped_methods #:nodoc: - Thread.current[:"#{self}_scoped_methods"] = nil + def build_default_scope #:nodoc: + if method(:default_scope).owner != Base.singleton_class + # Exclusively scope to just the relation, to avoid infinite recursion where the + # default scope tries to use the default scope tries to use the default scope... + relation.scoping { default_scope } + elsif default_scopes.any? + default_scopes.inject(relation) do |default_scope, scope| + if scope.is_a?(Hash) + default_scope.apply_finder_options(scope) + else + default_scope.merge(scope) + end + end + end end # Returns the class type of the record using the current module as a prefix. So descendants of @@ -1916,11 +1961,8 @@ MSG end def populate_with_current_scope_attributes - if scope = self.class.send(:current_scoped_methods) - create_with = scope.scope_for_create - create_with.each { |att,value| - respond_to?("#{att}=") && send("#{att}=", value) - } + self.class.scoped.scope_for_create.each do |att,value| + respond_to?("#{att}=") && send("#{att}=", value) end end diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index d5fff65303..a398476dd6 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -35,7 +35,7 @@ module ActiveRecord if options scoped.apply_finder_options(options) else - current_scoped_methods ? relation.merge(current_scoped_methods) : relation.clone + (current_scope || relation).clone end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 75269c85d9..645ad0fce1 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -154,12 +154,7 @@ module ActiveRecord # Please check unscoped if you want to remove all previous scopes (including # the default_scope) during the execution of a block. def scoping - @klass.scoped_methods << self - begin - yield - ensure - @klass.scoped_methods.pop - end + @klass.send(:with_scope, self, :overwrite) { yield } end # Updates all records with details given if they match a set of conditions supplied, limits and order can -- cgit v1.2.3 From 8572ae6671c6ec7c2524f327cee82215896e5648 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Fri, 8 Apr 2011 20:56:33 +0100 Subject: Evaluate default scopes at the last possible moment in order to avoid problems with default scopes getting included into other scopes and then being unable to remove the default part via unscoped. --- activerecord/lib/active_record/base.rb | 13 +++++-------- activerecord/lib/active_record/named_scope.rb | 8 +++++++- activerecord/lib/active_record/relation.rb | 17 ++++++++++++++--- .../lib/active_record/relation/query_methods.rb | 14 ++++++++++---- .../lib/active_record/relation/spawn_methods.rb | 4 ++++ 5 files changed, 40 insertions(+), 16 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index e0fb94c96e..08a41e2d8b 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1110,9 +1110,7 @@ module ActiveRecord #:nodoc: # If another Active Record class has been passed in, get its current scope scope = scope.current_scope if !scope.is_a?(Relation) && scope.respond_to?(:current_scope) - # Cannot use self.current_scope, because that could trigger build_default_scope, which - # could in turn result in with_scope being called and hence an infinite loop. - previous_scope = Thread.current[:"#{self}_current_scope"] + previous_scope = self.current_scope if scope.is_a?(Hash) # Dup first and second level of hash (method and params). @@ -1123,6 +1121,7 @@ module ActiveRecord #:nodoc: scope.assert_valid_keys([ :find, :create ]) relation = construct_finder_arel(scope[:find] || {}) + relation.default_scoped = true unless action == :overwrite if previous_scope && previous_scope.create_with_value && scope[:create] scope_for_create = if action == :merge @@ -1171,7 +1170,7 @@ MSG end def current_scope #:nodoc: - Thread.current[:"#{self}_current_scope"] ||= build_default_scope + Thread.current[:"#{self}_current_scope"] end def current_scope=(scope) #:nodoc: @@ -1227,15 +1226,13 @@ class Post < ActiveRecord::Base end WARN - # Reset the current scope as it may contain scopes based on a now-invalid default scope - self.current_scope = nil self.default_scopes = default_scopes.dup << scope end def build_default_scope #:nodoc: if method(:default_scope).owner != Base.singleton_class - # Exclusively scope to just the relation, to avoid infinite recursion where the - # default scope tries to use the default scope tries to use the default scope... + # Use relation.scoping to ensure we ignore whatever the current value of + # self.current_scope may be. relation.scoping { default_scope } elsif default_scopes.any? default_scopes.inject(relation) do |default_scope, scope| diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index a398476dd6..68e3018e22 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -35,7 +35,13 @@ module ActiveRecord if options scoped.apply_finder_options(options) else - (current_scope || relation).clone + if current_scope + current_scope.clone + else + scope = relation.clone + scope.default_scoped = true + scope + end end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 645ad0fce1..4aa2516cb2 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -6,7 +6,7 @@ module ActiveRecord JoinOperation = Struct.new(:relation, :join_class, :on) ASSOCIATION_METHODS = [:includes, :eager_load, :preload] MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having, :bind] - SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :create_with, :from] + SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :create_with, :from, :reorder] include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches @@ -15,14 +15,16 @@ module ActiveRecord delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :to => :klass attr_reader :table, :klass, :loaded - attr_accessor :extensions + attr_accessor :extensions, :default_scoped alias :loaded? :loaded + alias :default_scoped? :default_scoped def initialize(klass, table) @klass, @table = klass, table @implicit_readonly = nil @loaded = false + @default_scoped = false SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)} (ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])} @@ -372,7 +374,7 @@ module ActiveRecord end def where_values_hash - equalities = @where_values.grep(Arel::Nodes::Equality).find_all { |node| + equalities = with_default_scope.where_values.grep(Arel::Nodes::Equality).find_all { |node| node.left.relation.name == table_name } @@ -400,6 +402,15 @@ module ActiveRecord to_a.inspect end + def with_default_scope #:nodoc: + if default_scoped? + default_scope = @klass.send(:build_default_scope) + default_scope ? default_scope.merge(self) : self + else + self + end + end + protected def method_missing(method, *args, &block) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 02b7056989..94aa999715 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -8,7 +8,8 @@ module ActiveRecord attr_accessor :includes_values, :eager_load_values, :preload_values, :select_values, :group_values, :order_values, :joins_values, :where_values, :having_values, :bind_values, - :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value, :from_value + :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value, + :from_value, :reorder_value def includes(*args) args.reject! {|a| a.blank? } @@ -63,7 +64,11 @@ module ActiveRecord end def reorder(*args) - except(:order).order(args) + return self if args.blank? + + relation = clone + relation.reorder_value = args.flatten + relation end def joins(*args) @@ -163,7 +168,7 @@ module ActiveRecord end def arel - @arel ||= build_arel + @arel ||= with_default_scope.build_arel end def build_arel @@ -180,7 +185,8 @@ module ActiveRecord arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty? - arel.order(*@order_values.uniq.reject{|o| o.blank?}) unless @order_values.empty? + order = @reorder_value ? @reorder_value : @order_values + arel.order(*order.uniq.reject{|o| o.blank?}) unless order.empty? build_select(arel, @select_values.uniq) diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 128e0fbd86..69706b5ead 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -8,6 +8,8 @@ module ActiveRecord merged_relation = clone + r = r.with_default_scope if r.default_scoped? && r.klass != klass + Relation::ASSOCIATION_METHODS.each do |method| value = r.send(:"#{method}_values") @@ -70,6 +72,7 @@ module ActiveRecord # def except(*skips) result = self.class.new(@klass, table) + result.default_scoped = default_scoped ((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) - skips).each do |method| result.send(:"#{method}_values=", send(:"#{method}_values")) @@ -94,6 +97,7 @@ module ActiveRecord # def only(*onlies) result = self.class.new(@klass, table) + result.default_scoped = default_scoped ((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) & onlies).each do |method| result.send(:"#{method}_values=", send(:"#{method}_values")) -- cgit v1.2.3 From 788bd30859f3f21184defd240c3d32f179515225 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Fri, 8 Apr 2011 23:53:25 +0100 Subject: ActiveRecord::Base.scopes hash is not needed --- activerecord/lib/active_record/named_scope.rb | 20 ++++---------------- activerecord/lib/active_record/relation.rb | 2 -- 2 files changed, 4 insertions(+), 18 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 68e3018e22..60840e6958 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -9,11 +9,6 @@ module ActiveRecord module NamedScope extend ActiveSupport::Concern - included do - class_attribute :scopes - self.scopes = {} - end - module ClassMethods # Returns an anonymous \scope. # @@ -135,27 +130,20 @@ module ActiveRecord scope_proc = lambda do |*args| options = scope_options.respond_to?(:call) ? scope_options.call(*args) : scope_options + options = scoped.apply_finder_options(options) if options.is_a?(Hash) - relation = if options.is_a?(Hash) - scoped.apply_finder_options(options) - elsif options - scoped.merge(options) - else - scoped - end + relation = scoped.merge(options) extension ? relation.extending(extension) : relation end - self.scopes = self.scopes.merge name => scope_proc - - singleton_class.send(:redefine_method, name, &scopes[name]) + singleton_class.send(:redefine_method, name, &scope_proc) end protected def valid_scope_name?(name) - if !scopes[name] && respond_to?(name, true) + if respond_to?(name, true) logger.warn "Creating scope :#{name}. " \ "Overwriting existing method #{self.name}.#{name}." end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 4aa2516cb2..490360ccb5 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -416,8 +416,6 @@ module ActiveRecord def method_missing(method, *args, &block) if Array.method_defined?(method) to_a.send(method, *args, &block) - elsif @klass.scopes[method] - merge(@klass.send(method, *args, &block)) elsif @klass.respond_to?(method) scoping { @klass.send(method, *args, &block) } elsif arel.respond_to?(method) -- cgit v1.2.3 From f0e198bfa1e3f9689e0cde1d194a44027fc90b3c Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Fri, 8 Apr 2011 23:54:54 +0100 Subject: Deprecate defining scopes with a callable (lambda, proc, etc) via the scope class method. Just define a class method yourself instead. --- activerecord/lib/active_record/named_scope.rb | 62 ++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 60840e6958..f1df04950b 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -51,6 +51,14 @@ module ActiveRecord # The above calls to scope define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red, # in effect, represents the query Shirt.where(:color => 'red'). # + # Note that this is simply 'syntactic sugar' for defining an actual class method: + # + # class Shirt < ActiveRecord::Base + # def self.red + # where(:color => 'red') + # end + # end + # # Unlike Shirt.find(...), however, the object returned by Shirt.red is not an Array; it # resembles the association object constructed by a has_many declaration. For instance, # you can invoke Shirt.red.first, Shirt.red.count, Shirt.red.where(:size => 'small'). @@ -74,14 +82,34 @@ module ActiveRecord # then elton.shirts.red.dry_clean_only will return all of Elton's red, dry clean # only shirts. # - # Named \scopes can also be procedural: + # If you need to pass parameters to a scope, define it as a normal method: # # class Shirt < ActiveRecord::Base - # scope :colored, lambda {|color| where(:color => color) } + # def self.colored(color) + # where(:color => color) + # end # end # # In this example, Shirt.colored('puce') finds all puce shirts. # + # Note that scopes defined with \scope will be evaluated when they are defined, rather than + # when they are used. For example, the following would be incorrect: + # + # class Post < ActiveRecord::Base + # scope :recent, where('published_at >= ?', Time.now - 1.week) + # end + # + # The example above would be 'frozen' to the Time.now value when the Post + # class was defined, and so the resultant SQL query would always be the same. The correct + # way to do this would be via a class method, which will re-evaluate the scope each time + # it is called: + # + # class Post < ActiveRecord::Base + # def self.recent + # where('published_at >= ?', Time.now - 1.week) + # end + # end + # # Named \scopes can also have extensions, just as with has_many declarations: # # class Shirt < ActiveRecord::Base @@ -92,6 +120,18 @@ module ActiveRecord # end # end # + # The above could also be written as a class method like so: + # + # class Shirt < ActiveRecord::Base + # def self.red + # where(:color => 'red').extending do + # def dom_id + # 'red_shirts' + # end + # end + # end + # end + # # Scopes can also be used while creating/building a record. # # class Article < ActiveRecord::Base @@ -128,6 +168,24 @@ module ActiveRecord valid_scope_name?(name) extension = Module.new(&Proc.new) if block_given? + if !scope_options.is_a?(Relation) && scope_options.respond_to?(:call) + ActiveSupport::Deprecation.warn <<-WARN +Passing a proc (or other object that responds to #call) to scope is deprecated. If you need your scope to be lazily evaluated, or takes parameters, please define it as a normal class method instead. For example, change this: + +class Post < ActiveRecord::Base + scope :unpublished, lambda { where('published_at > ?', Time.now) } +end + +To this: + +class Post < ActiveRecord::Base + def self.unpublished + where('published_at > ?', Time.now) + end +end + WARN + end + scope_proc = lambda do |*args| options = scope_options.respond_to?(:call) ? scope_options.call(*args) : scope_options options = scoped.apply_finder_options(options) if options.is_a?(Hash) -- cgit v1.2.3