aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb45
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb23
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb5
-rw-r--r--activerecord/lib/active_record/core.rb14
-rw-r--r--activerecord/lib/active_record/relation.rb10
-rw-r--r--activerecord/test/cases/attribute_methods/read_test.rb11
-rw-r--r--activerecord/test/cases/relations_test.rb8
7 files changed, 73 insertions, 43 deletions
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 609c6e8cab..2fd2ea896b 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/enumerable'
+require 'mutex_m'
module ActiveRecord
# = Active Record Attribute Methods
@@ -7,6 +8,7 @@ module ActiveRecord
include ActiveModel::AttributeMethods
included do
+ initialize_generated_modules
include Read
include Write
include BeforeTypeCast
@@ -17,27 +19,46 @@ module ActiveRecord
include Serialization
end
+ AttrNames = Module.new {
+ def self.set_name_cache(name, value)
+ const_name = "ATTR_#{name}"
+ unless const_defined? const_name
+ const_set const_name, value.dup.freeze
+ end
+ end
+ }
+
module ClassMethods
+ def inherited(child_class) #:nodoc:
+ child_class.initialize_generated_modules
+ super
+ end
+
+ def initialize_generated_modules # :nodoc:
+ @generated_attribute_methods = Module.new { extend Mutex_m }
+ @attribute_methods_generated = false
+ include @generated_attribute_methods
+ end
+
# Generates all the attribute related methods for columns in the database
# accessors, mutators and query methods.
def define_attribute_methods # :nodoc:
# Use a mutex; we don't want two thread simultaneously trying to define
# attribute methods.
- @attribute_methods_mutex.synchronize do
- return if attribute_methods_generated?
+ generated_attribute_methods.synchronize do
+ return false if @attribute_methods_generated
superclass.define_attribute_methods unless self == base_class
super(column_names)
@attribute_methods_generated = true
end
- end
-
- def attribute_methods_generated? # :nodoc:
- @attribute_methods_generated ||= false
+ true
end
def undefine_attribute_methods # :nodoc:
- super if attribute_methods_generated?
- @attribute_methods_generated = false
+ generated_attribute_methods.synchronize do
+ super if @attribute_methods_generated
+ @attribute_methods_generated = false
+ end
end
# Raises a <tt>ActiveRecord::DangerousAttributeError</tt> exception when an
@@ -119,9 +140,7 @@ module ActiveRecord
# If we haven't generated any methods yet, generate them, then
# see if we've created the method we're looking for.
def method_missing(method, *args, &block) # :nodoc:
- unless self.class.attribute_methods_generated?
- self.class.define_attribute_methods
-
+ if self.class.define_attribute_methods
if respond_to_without_attributes?(method)
send(method, *args, &block)
else
@@ -164,7 +183,7 @@ module ActiveRecord
# person.respond_to(:nothing) # => false
def respond_to?(name, include_private = false)
name = name.to_s
- self.class.define_attribute_methods unless self.class.attribute_methods_generated?
+ self.class.define_attribute_methods
result = super
# If the result is false the answer is false.
@@ -174,7 +193,7 @@ module ActiveRecord
# For queries selecting a subset of columns, return false for unselected columns.
# We check defined?(@attributes) not to issue warnings if called on objects that
# have been allocated but not yet initialized.
- if defined?(@attributes) && @attributes.present? && self.class.column_names.include?(name)
+ if defined?(@attributes) && @attributes.any? && self.class.column_names.include?(name)
return has_attribute?(name)
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 506f5d75f9..684fe2dc05 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -33,8 +33,11 @@ module ActiveRecord
protected
# We want to generate the methods via module_eval rather than
- # define_method, because define_method is slower on dispatch and
- # uses more memory (because it creates a closure).
+ # define_method, because define_method is slower on dispatch.
+ # Evaluating many similar methods may use more memory as the instruction
+ # sequences are duplicated and cached (in MRI). define_method may
+ # be slower on dispatch, but if you're careful about the closure
+ # created, then define_method will consume much less memory.
#
# But sometimes the database might return columns with
# characters that are not allowed in normal method names (like
@@ -49,13 +52,21 @@ module ActiveRecord
# key the @attributes_cache in read_attribute.
def define_method_attribute(name)
safe_name = name.unpack('h*').first
+ temp_method = "__temp__#{safe_name}"
+
+ ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
+
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
- def __temp__#{safe_name}
- read_attribute(AttrNames::ATTR_#{safe_name}) { |n| missing_attribute(n, caller) }
+ def #{temp_method}
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
+ read_attribute(name) { |n| missing_attribute(n, caller) }
end
- alias_method #{name.inspect}, :__temp__#{safe_name}
- undef_method :__temp__#{safe_name}
STR
+
+ generated_attribute_methods.module_eval do
+ alias_method name, temp_method
+ undef_method temp_method
+ end
end
private
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index cd33494cc3..9c7f643283 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -14,9 +14,12 @@ module ActiveRecord
# this code.
def define_method_attribute=(name)
safe_name = name.unpack('h*').first
+ ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
+
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
def __temp__#{safe_name}=(value)
- write_attribute(AttrNames::ATTR_#{safe_name}, value)
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
+ write_attribute(name, value)
end
alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
undef_method :__temp__#{safe_name}=
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index f306243552..c6b7da2e3c 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -88,20 +88,8 @@ module ActiveRecord
end
module ClassMethods
- def inherited(child_class) #:nodoc:
- child_class.initialize_generated_modules
- super
- end
-
def initialize_generated_modules
- @attribute_methods_mutex = Mutex.new
-
- # force attribute methods to be higher in inheritance hierarchy than other generated methods
- generated_attribute_methods.const_set(:AttrNames, Module.new {
- def self.const_missing(name)
- const_set(name, [name.to_s.sub(/ATTR_/, '')].pack('h*').freeze)
- end
- })
+ super
generated_feature_methods
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 18fdac2600..612f376c55 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -501,7 +501,15 @@ module ActiveRecord
# User.where(name: 'Oscar').to_sql
# # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
def to_sql
- @to_sql ||= klass.connection.to_sql(arel, bind_values.dup)
+ @to_sql ||= begin
+ if eager_loading?
+ join_dependency = construct_join_dependency
+ relation = construct_relation_for_association_find(join_dependency)
+ klass.connection.to_sql(relation.arel, relation.bind_values)
+ else
+ klass.connection.to_sql(arel, bind_values.dup)
+ end
+ end
end
# Returns a hash of where conditions.
diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb
index 8d8ff2f952..c0659fddef 100644
--- a/activerecord/test/cases/attribute_methods/read_test.rb
+++ b/activerecord/test/cases/attribute_methods/read_test.rb
@@ -15,13 +15,6 @@ module ActiveRecord
include ActiveRecord::AttributeMethods
- def self.define_attribute_methods
- # Created in the inherited/included hook for "proper" ARs
- @attribute_methods_mutex ||= Mutex.new
-
- super
- end
-
def self.column_names
%w{ one two three }
end
@@ -56,9 +49,9 @@ module ActiveRecord
end
def test_attribute_methods_generated?
- assert(!@klass.attribute_methods_generated?, 'attribute_methods_generated?')
+ assert_not @klass.method_defined?(:one)
@klass.define_attribute_methods
- assert(@klass.attribute_methods_generated?, 'attribute_methods_generated?')
+ assert @klass.method_defined?(:one)
end
end
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 7a6d9be515..b205472cf5 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -481,6 +481,14 @@ class RelationTest < ActiveRecord::TestCase
assert_equal Post.find(1).last_comment, post.last_comment
end
+ def test_to_sql_on_eager_join
+ expected = assert_sql {
+ Post.eager_load(:last_comment).order('comments.id DESC').to_a
+ }.first
+ actual = Post.eager_load(:last_comment).order('comments.id DESC').to_sql
+ assert_equal expected, actual
+ end
+
def test_loading_with_one_association_with_non_preload
posts = Post.eager_load(:last_comment).order('comments.id DESC')
post = posts.find { |p| p.id == 1 }