diff options
Diffstat (limited to 'activerecord/lib')
14 files changed, 83 insertions, 107 deletions
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 253998fb23..b4c3908b10 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -77,7 +77,7 @@ module ActiveRecord # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000) # Make several smaller queries if necessary or make one query if the adapter supports it sliced = owner_keys.each_slice(model.connection.in_clause_length || owner_keys.size) - records = sliced.map { |slice| records_for(slice) }.flatten + records = sliced.map { |slice| records_for(slice).to_a }.flatten end # Each record may have multiple owners, and vice-versa @@ -93,7 +93,8 @@ module ActiveRecord end def build_scope - scope = klass.scoped + scope = klass.unscoped + scope.default_scoped = true scope = scope.where(interpolate(options[:conditions])) scope = scope.where(interpolate(preload_options[:conditions])) diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index bebf6a1a05..39ea885246 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -49,14 +49,6 @@ module ActiveRecord @attribute_methods_generated ||= false end - # We will define the methods as instance methods, but will call them as singleton - # methods. This allows us to use method_defined? to check if the method exists, - # which is fast and won't give any false positives from the ancestors (because - # there are no ancestors). - def generated_external_attribute_methods - @generated_external_attribute_methods ||= Module.new { extend self } - end - def undefine_attribute_methods super if attribute_methods_generated? @attribute_methods_generated = false diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 51389c84d6..7b7811a706 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -43,12 +43,6 @@ module ActiveRecord if attr_name == primary_key && attr_name != 'id' generated_attribute_methods.send(:alias_method, :id, primary_key) - generated_external_attribute_methods.module_eval <<-CODE, __FILE__, __LINE__ - def id(v, attributes, attributes_cache, attr_name) - attr_name = '#{primary_key}' - send(attr_name, attributes[attr_name], attributes, attributes_cache, attr_name) - end - CODE end end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 846ac03d82..dcc3d79de9 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -29,35 +29,8 @@ module ActiveRecord cached_attributes.include?(attr_name) end - def undefine_attribute_methods - generated_external_attribute_methods.module_eval do - instance_methods.each { |m| undef_method(m) } - end - - super - end - - def type_cast_attribute(attr_name, attributes, cache = {}) #:nodoc: - return unless attr_name - attr_name = attr_name.to_s - - if generated_external_attribute_methods.method_defined?(attr_name) - if attributes.has_key?(attr_name) || attr_name == 'id' - generated_external_attribute_methods.send(attr_name, attributes[attr_name], attributes, cache, attr_name) - end - elsif !attribute_methods_generated? - # If we haven't generated the caster methods yet, do that and - # then try again - define_attribute_methods - type_cast_attribute(attr_name, attributes, cache) - else - # If we get here, the attribute has no associated DB column, so - # just return it verbatim. - attributes[attr_name] - end - end - 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). @@ -67,19 +40,9 @@ module ActiveRecord # we first define with the __temp__ identifier, and then use alias method to # rename it to what we want. def define_method_attribute(attr_name) - cast_code = attribute_cast_code(attr_name) - generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 def __temp__ - #{internal_attribute_access_code(attr_name, cast_code)} - end - alias_method '#{attr_name}', :__temp__ - undef_method :__temp__ - STR - - generated_external_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 - def __temp__(v, attributes, attributes_cache, attr_name) - #{external_attribute_access_code(attr_name, cast_code)} + read_attribute('#{attr_name}') { |n| missing_attribute(n, caller) } end alias_method '#{attr_name}', :__temp__ undef_method :__temp__ @@ -87,6 +50,7 @@ module ActiveRecord end private + def cacheable_column?(column) if attribute_types_cached_by_default == ATTRIBUTE_TYPES_CACHED_BY_DEFAULT ! serialized_attributes.include? column.name @@ -94,33 +58,19 @@ module ActiveRecord attribute_types_cached_by_default.include?(column.type) end end - - def internal_attribute_access_code(attr_name, cast_code) - "read_attribute('#{attr_name}') { |n| missing_attribute(n, caller) }" - end - - def external_attribute_access_code(attr_name, cast_code) - access_code = "v && #{cast_code}" - - if cache_attribute?(attr_name) - access_code = "attributes_cache[attr_name] ||= (#{access_code})" - end - - access_code - end - - def attribute_cast_code(attr_name) - columns_hash[attr_name].type_cast_code('v') - end end # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example, # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). def read_attribute(attr_name) # If it's cached, just return it - @attributes_cache.fetch(attr_name) { |name| + @attributes_cache.fetch(attr_name.to_s) { |name| column = @columns_hash.fetch(name) { - return self.class.type_cast_attribute(name, @attributes, @attributes_cache) + return @attributes.fetch(name) { + if name == 'id' && self.class.primary_key != name + read_attribute(self.class.primary_key) + end + } } value = @attributes.fetch(name) { diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 731c07547a..64f922b7ad 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -526,7 +526,7 @@ module ActiveRecord execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result| create_table = each_hash(result).first[:"Create Table"] if create_table.to_s =~ /PRIMARY KEY\s+\((.+)\)/ - keys = $1.split(",").map { |key| key.gsub(/[`"]/, "") } + keys = $1.split(",").map { |key| key.delete('`"') } keys.length == 1 ? [keys.first, nil] : nil else nil diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 78e54c4c9b..b7e1513422 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -1,4 +1,5 @@ require 'set' +require 'active_support/deprecation' module ActiveRecord # :stopdoc: @@ -107,6 +108,9 @@ module ActiveRecord end def type_cast_code(var_name) + ActiveSupport::Deprecation.warn("Column#type_cast_code is deprecated in favor of" \ + "using Column#type_cast only, and it is going to be removed in future Rails versions.") + klass = self.class.name case type diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 9a2f859fc7..76c424e8b4 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -1,4 +1,5 @@ require 'active_support/concern' +require 'active_support/core_ext/hash/indifferent_access' require 'thread' module ActiveRecord @@ -326,6 +327,11 @@ module ActiveRecord "#<#{self.class} #{inspection}>" end + # Returns a hash of the given methods with their names as keys and returned values as values. + def slice(*methods) + Hash[methods.map { |method| [method, public_send(method)] }].with_indifferent_access + end + private # Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 8266427b71..a3412582fa 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -101,24 +101,29 @@ module ActiveRecord end end - def destroy #:nodoc: - return super unless locking_enabled? + def destroy_row + affected_rows = super - if persisted? - table = self.class.arel_table - lock_col = self.class.locking_column - predicate = table[self.class.primary_key].eq(id). - and(table[lock_col].eq(send(lock_col).to_i)) + if locking_enabled? && affected_rows != 1 + raise ActiveRecord::StaleObjectError.new(self, "destroy") + end - affected_rows = self.class.unscoped.where(predicate).delete_all + affected_rows + end - unless affected_rows == 1 - raise ActiveRecord::StaleObjectError.new(self, "destroy") - end + def relation_for_destroy + relation = super + + if locking_enabled? + column_name = self.class.locking_column + column = self.class.columns_hash[column_name] + substitute = connection.substitute_at(column, relation.bind_values.length) + + relation = relation.where(self.class.arel_table[column_name].eq(substitute)) + relation.bind_values << [column, self[column_name].to_i] end - @destroyed = true - freeze + relation end module ClassMethods diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 6bf0becad8..32a1dae6bc 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -288,7 +288,7 @@ module ActiveRecord # def pirate_attributes=(attributes) # assign_nested_attributes_for_one_to_one_association(:pirate, attributes, mass_assignment_options) # end - class_eval <<-eoruby, __FILE__, __LINE__ + 1 + generated_feature_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1 if method_defined?(:#{association_name}_attributes=) remove_method(:#{association_name}_attributes=) end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 35c922e979..bb504ae90f 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -124,19 +124,7 @@ module ActiveRecord # that no changes should be made (since they can't be persisted). def destroy destroy_associations - - if persisted? - pk = self.class.primary_key - column = self.class.columns_hash[pk] - substitute = connection.substitute_at(column, 0) - - relation = self.class.unscoped.where( - self.class.arel_table[pk].eq(substitute)) - - relation.bind_values = [[column, id]] - relation.delete_all - end - + destroy_row if persisted? @destroyed = true freeze end @@ -335,6 +323,22 @@ module ActiveRecord def destroy_associations end + def destroy_row + relation_for_destroy.delete_all + end + + def relation_for_destroy + pk = self.class.primary_key + column = self.class.columns_hash[pk] + substitute = connection.substitute_at(column, 0) + + relation = self.class.unscoped.where( + self.class.arel_table[pk].eq(substitute)) + + relation.bind_values = [[column, id]] + relation + end + def create_or_update raise ReadOnlyRecord if readonly? result = new_record? ? create : update diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index 0e6fecbc4b..95565b503a 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -5,6 +5,7 @@ module ActiveRecord module Querying delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped delegate :first_or_create, :first_or_create!, :first_or_initialize, :to => :scoped + delegate :find_by, :find_by!, :to => :scoped delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped delegate :find_each, :find_in_batches, :to => :scoped delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 2c74f4011d..74f8e30404 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -109,6 +109,25 @@ module ActiveRecord end end + # Finds the first record matching the specified conditions. There + # is no implied ording so if order matters, you should specify it + # yourself. + # + # If no record is found, returns <tt>nil</tt>. + # + # Post.find_by name: 'Spartacus', rating: 4 + # Post.find_by "published_at < ?", 2.weeks.ago + # + def find_by(*args) + where(*args).first + end + + # Like <tt>find_by</tt>, except that if no record is found, raises + # an <tt>ActiveRecord::RecordNotFound</tt> error. + def find_by!(*args) + where(*args).first! + end + # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the # same arguments to this method as you can to <tt>find(:first)</tt>. def first(*args) diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 1088773bc7..b40bf2b3cf 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -39,7 +39,7 @@ module ActiveRecord attribute.in(value.arel.ast) when Array, ActiveRecord::Associations::CollectionProxy values = value.to_a.map {|x| x.is_a?(ActiveRecord::Model) ? x.id : x} - ranges, values = values.partition {|v| v.is_a?(Range) || v.is_a?(Arel::Relation)} + ranges, values = values.partition {|v| v.is_a?(Range)} values_predicate = if values.include?(nil) values = values.compact @@ -59,7 +59,7 @@ module ActiveRecord array_predicates = ranges.map { |range| attribute.in(range) } array_predicates << values_predicate array_predicates.inject { |composite, predicate| composite.or(predicate) } - when Range, Arel::Relation + when Range attribute.in(value) when ActiveRecord::Model attribute.eq(value.id) diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb index 9ca63a209e..b9b5ec7956 100644 --- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb +++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb @@ -13,9 +13,9 @@ class <%= migration_class_name %> < ActiveRecord::Migration <% attributes.each do |attribute| -%> <%- if migration_action -%> <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type %><%= attribute.inject_options %><% end %> - <% if attribute.has_index? && migration_action == 'add' %> + <%- if attribute.has_index? && migration_action == 'add' -%> add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> - <% end -%> + <%- end -%> <%- end -%> <%- end -%> end |