diff options
Diffstat (limited to 'activerecord/lib')
42 files changed, 340 insertions, 215 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 5e5995f566..3490057298 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -172,7 +172,7 @@ module ActiveRecord @association_cache[name] = association end - # Associations are a set of macro-like class methods for tying objects together through + # \Associations are a set of macro-like class methods for tying objects together through # foreign keys. They express relationships like "Project has one Project Manager" # or "Project belongs to a Portfolio". Each macro adds a number of methods to the # class which are specialized according to the collection or association symbol and the @@ -365,11 +365,11 @@ module ActiveRecord # there is some special behavior you should be aware of, mostly involving the saving of # associated objects. # - # You can set the :autosave option on a <tt>has_one</tt>, <tt>belongs_to</tt>, + # You can set the <tt>:autosave</tt> option on a <tt>has_one</tt>, <tt>belongs_to</tt>, # <tt>has_many</tt>, or <tt>has_and_belongs_to_many</tt> association. Setting it # to +true+ will _always_ save the members, whereas setting it to +false+ will - # _never_ save the members. More details about :autosave option is available at - # autosave_association.rb . + # _never_ save the members. More details about <tt>:autosave</tt> option is available at + # AutosaveAssociation. # # === One-to-one associations # @@ -402,7 +402,7 @@ module ActiveRecord # # == Customizing the query # - # Associations are built from <tt>Relation</tt>s, and you can use the <tt>Relation</tt> syntax + # \Associations are built from <tt>Relation</tt>s, and you can use the <tt>Relation</tt> syntax # to customize them. For example, to add a condition: # # class Blog < ActiveRecord::Base @@ -568,6 +568,8 @@ module ActiveRecord # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around # @group.avatars.delete(@group.avatars.last) # so would this # + # == Setting Inverses + # # If you are using a +belongs_to+ on the join model, it is a good idea to set the # <tt>:inverse_of</tt> option on the +belongs_to+, which will mean that the following example # works correctly (where <tt>tags</tt> is a +has_many+ <tt>:through</tt> association): @@ -584,7 +586,26 @@ module ActiveRecord # belongs_to :tag, inverse_of: :taggings # end # - # == Nested Associations + # If you do not set the +:inverse_of+ record, the association will do its + # best to match itself up with the correct inverse. Automatic +:inverse_of+ + # detection only works on +has_many+, +has_one+, and +belongs_to+ associations. + # + # Extra options on the associations, as defined in the + # <tt>AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS</tt> constant, will + # also prevent the association's inverse from being found automatically. + # + # The automatic guessing of the inverse association uses a heuristic based + # on the name of the class, so it may not work for all associations, + # especially the ones with non-standard names. + # + # You can turn off the automatic detection of inverse associations by setting + # the +:automatic_inverse_of+ option to +false+ like so: + # + # class Taggable < ActiveRecord::Base + # belongs_to :tag, automatic_inverse_of: false + # end + # + # == Nested \Associations # # You can actually specify *any* association with the <tt>:through</tt> option, including an # association which has a <tt>:through</tt> option itself. For example: @@ -627,7 +648,7 @@ module ActiveRecord # add a <tt>Commenter</tt> in the example above, there would be no way to tell how to set up the # intermediate <tt>Post</tt> and <tt>Comment</tt> objects. # - # == Polymorphic Associations + # == Polymorphic \Associations # # Polymorphic associations on models are not restricted on what types of models they # can be associated with. Rather, they specify an interface that a +has_many+ association @@ -789,7 +810,7 @@ module ActiveRecord # For example if all the addressables are either of class Person or Company then a total # of 3 queries will be executed. The list of addressable types to load is determined on # the back of the addresses loaded. This is not supported if Active Record has to fallback - # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. + # to the previous implementation of eager loading and will raise <tt>ActiveRecord::EagerLoadPolymorphicError</tt>. # The reason is that the parent model's type is a column value so its corresponding table # name cannot be put in the +FROM+/+JOIN+ clauses of that query. # @@ -1024,7 +1045,7 @@ module ActiveRecord # An empty array is returned if none are found. # [collection<<(object, ...)] # Adds one or more objects to the collection by setting their foreign keys to the collection's primary key. - # Note that this operation instantly fires update sql without waiting for the save or update call on the + # Note that this operation instantly fires update SQL without waiting for the save or update call on the # parent object, unless the parent object is a new record. # [collection.delete(object, ...)] # Removes one or more objects from the collection by setting their foreign keys to +NULL+. @@ -1060,10 +1081,10 @@ module ActiveRecord # [collection.size] # Returns the number of associated objects. # [collection.find(...)] - # Finds an associated object according to the same rules as ActiveRecord::Base.find. + # Finds an associated object according to the same rules as <tt>ActiveRecord::Base.find</tt>. # [collection.exists?(...)] # Checks whether an associated object with the given conditions exists. - # Uses the same rules as ActiveRecord::Base.exists?. + # Uses the same rules as <tt>ActiveRecord::Base.exists?</tt>. # [collection.build(attributes = {}, ...)] # Returns one or more new objects of the collection type that have been instantiated # with +attributes+ and linked to this object through a foreign key, but have not yet @@ -1082,7 +1103,7 @@ module ActiveRecord # # === Example # - # Example: A Firm class declares <tt>has_many :clients</tt>, which will add: + # A <tt>Firm</tt> class declares <tt>has_many :clients</tt>, which will add: # * <tt>Firm#clients</tt> (similar to <tt>Client.where(firm_id: id)</tt>) # * <tt>Firm#clients<<</tt> # * <tt>Firm#clients.delete</tt> @@ -1116,8 +1137,8 @@ module ActiveRecord # Controls what happens to the associated objects when # their owner is destroyed. Note that these are implemented as # callbacks, and Rails executes callbacks in order. Therefore, other - # similar callbacks may affect the :dependent behavior, and the - # :dependent behavior may affect other callbacks. + # similar callbacks may affect the <tt>:dependent</tt> behavior, and the + # <tt>:dependent</tt> behavior may affect other callbacks. # # * <tt>:destroy</tt> causes all the associated objects to also be destroyed. # * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not be executed). @@ -1163,8 +1184,8 @@ module ActiveRecord # If true, always save the associated objects or destroy them if marked for destruction, # when saving the parent object. If false, never save or destroy the associated objects. # By default, only save associated objects that are new records. This option is implemented as a - # before_save callback. Because callbacks are run in the order they are defined, associated objects - # may need to be explicitly saved in any user-defined before_save callbacks. + # +before_save+ callback. Because callbacks are run in the order they are defined, associated objects + # may need to be explicitly saved in any user-defined +before_save+ callbacks. # # Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>. # [:inverse_of] @@ -1189,7 +1210,7 @@ module ActiveRecord # Specifies a one-to-one association with another class. This method should only be used # if the other class contains the foreign key. If the current class contains the foreign key, # then you should use +belongs_to+ instead. See also ActiveRecord::Associations::ClassMethods's overview - # on when to use has_one and when to use belongs_to. + # on when to use +has_one+ and when to use +belongs_to+. # # The following methods for retrieval and query of a single associated object will be added: # @@ -1357,7 +1378,7 @@ module ActiveRecord # class is created and decremented when it's destroyed. This requires that a column # named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class) # is used on the associate class (such as a Post class) - that is the migration for - # <tt>#{table_name}_count</tt> is created on the associate class (such that Post.comments_count will + # <tt>#{table_name}_count</tt> is created on the associate class (such that <tt>Post.comments_count</tt> will # return the count cached, see note below). You can also specify a custom counter # cache column by providing a column name instead of a +true+/+false+ value to this # option (e.g., <tt>counter_cache: :my_custom_counter</tt>.) @@ -1439,7 +1460,7 @@ module ActiveRecord # [collection<<(object, ...)] # Adds one or more objects to the collection by creating associations in the join table # (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method). - # Note that this operation instantly fires update sql without waiting for the save or update call on the + # Note that this operation instantly fires update SQL without waiting for the save or update call on the # parent object, unless the parent object is a new record. # [collection.delete(object, ...)] # Removes one or more objects from the collection by removing their associations from the join table. @@ -1462,10 +1483,10 @@ module ActiveRecord # [collection.find(id)] # Finds an associated object responding to the +id+ and that # meets the condition that it has to be associated with this object. - # Uses the same rules as ActiveRecord::Base.find. + # Uses the same rules as <tt>ActiveRecord::Base.find</tt>. # [collection.exists?(...)] # Checks whether an associated object with the given conditions exists. - # Uses the same rules as ActiveRecord::Base.exists?. + # Uses the same rules as <tt>ActiveRecord::Base.exists?</tt>. # [collection.build(attributes = {})] # Returns a new object of the collection type that has been instantiated # with +attributes+ and linked to this object through the join table, but has not yet been saved. diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb index 5c37f42794..6a3658f328 100644 --- a/activerecord/lib/active_record/associations/builder/association.rb +++ b/activerecord/lib/active_record/associations/builder/association.rb @@ -1,3 +1,15 @@ +# This is the parent Association class which defines the variables +# used by all associations. +# +# The hierarchy is defined as follows: +# Association +# - SingularAssociation +# - BelongsToAssociation +# - HasOneAssociation +# - CollectionAssociation +# - HasManyAssociation +# - HasAndBelongsToManyAssociation + module ActiveRecord::Associations::Builder class Association #:nodoc: class << self @@ -58,6 +70,13 @@ module ActiveRecord::Associations::Builder def validate_options options.assert_valid_keys(valid_options) end + + # Defines the setter and getter methods for the association + # class Post < ActiveRecord::Base + # has_many :comments + # end + # + # Post.first.comments and Post.first.comments= methods are defined by this method... def define_accessors define_readers diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb index fdead16761..9c6690b721 100644 --- a/activerecord/lib/active_record/associations/builder/collection_association.rb +++ b/activerecord/lib/active_record/associations/builder/collection_association.rb @@ -1,3 +1,5 @@ +# This class is inherited by the has_many and has_many_and_belongs_to_many association classes + require 'active_record/associations' module ActiveRecord::Associations::Builder @@ -66,6 +68,8 @@ module ActiveRecord::Associations::Builder model.send("#{full_callback_name}=", Array(options[callback_name.to_sym])) end + # Defines the setter and getter methods for the collection_singular_ids. + def define_readers super diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb index f06426a09d..96ccbeb8a3 100644 --- a/activerecord/lib/active_record/associations/builder/singular_association.rb +++ b/activerecord/lib/active_record/associations/builder/singular_association.rb @@ -1,3 +1,5 @@ +# This class is inherited by the has_one and belongs_to association classes + module ActiveRecord::Associations::Builder class SingularAssociation < Association #:nodoc: def valid_options @@ -13,6 +15,8 @@ module ActiveRecord::Associations::Builder define_constructors if constructable? end + # Defines the (build|create)_association methods for belongs_to or has_one association + def define_constructors mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def build_#{name}(*args, &block) diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 2a00ac1386..efd7ecb97c 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -237,11 +237,11 @@ module ActiveRecord end end - # Destroy +records+ and remove them from this association calling - # +before_remove+ and +after_remove+ callbacks. + # Deletes the +records+ and removes them from this association calling + # +before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks. # - # Note that this method will _always_ remove records from the database - # ignoring the +:dependent+ option. + # Note that this method removes records from the database ignoring the + # +:dependent+ option. def destroy(*records) records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) } delete_or_destroy(records, :destroy) diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 71b64de5ea..e82c195335 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -422,9 +422,9 @@ module ActiveRecord @association.delete_all end - # Deletes the records of the collection directly from the database. - # This will _always_ remove the records ignoring the +:dependent+ - # option. + # Deletes the records of the collection directly from the database + # ignoring the +:dependent+ option. It invokes +before_remove+, + # +after_remove+ , +before_destroy+ and +after_destroy+ callbacks. # # class Person < ActiveRecord::Base # has_many :pets diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index 29fae809da..cf8a589496 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -115,8 +115,7 @@ module ActiveRecord if records == :all scope = self.scope else - keys = records.map { |r| r[reflection.association_primary_key] } - scope = self.scope.where(reflection.association_primary_key => keys) + scope = self.scope.where(reflection.klass.primary_key => records) end if method == :delete_all diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index 5b2f2d1902..5aa17e5fbb 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -48,7 +48,7 @@ module ActiveRecord end def join_associations - join_parts.last(join_parts.length - 1) + join_parts.drop 1 end def join_base @@ -56,10 +56,9 @@ module ActiveRecord end def join_relation(relation) - join_associations.each do |association| - relation = association.join_relation(relation) + join_associations.inject(relation) do |rel,association| + association.join_relation(rel) end - relation end def columns @@ -132,8 +131,7 @@ module ActiveRecord ref[association.reflection.name] ||= {} end - def build(associations, parent = nil, join_type = Arel::InnerJoin) - parent ||= join_parts.last + def build(associations, parent = join_parts.last, join_type = Arel::InnerJoin) case associations when Symbol, String reflection = parent.reflections[associations.intern] or diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb index e4d17451dc..b81aecb4e5 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -106,12 +106,16 @@ module ActiveRecord ] end - scope_chain_items.each do |item| + constraint = scope_chain_items.inject(constraint) do |chain, item| unless item.is_a?(Relation) item = ActiveRecord::Relation.new(reflection.klass, table).instance_exec(self, &item) end - constraint = constraint.and(item.arel.constraints) unless item.arel.constraints.empty? + if item.arel.constraints.empty? + chain + else + chain.and(item.arel.constraints) + end end manager.from(join(table, constraint)) diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index e536f5ebcc..75377bba57 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -1,3 +1,4 @@ +require 'active_model/forbidden_attributes_protection' module ActiveRecord module AttributeAssignment @@ -44,7 +45,7 @@ module ActiveRecord if respond_to?("#{k}=") raise else - raise UnknownAttributeError, "unknown attribute: #{k}" + raise UnknownAttributeError.new(self, k) end end diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb index a23baeaced..f596a8b02e 100644 --- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb +++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb @@ -41,8 +41,9 @@ module ActiveRecord # task.read_attribute_before_type_cast('id') # => '1' # task.read_attribute('completed_on') # => Sun, 21 Oct 2012 # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21" + # task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21" def read_attribute_before_type_cast(attr_name) - @attributes[attr_name] + @attributes[attr_name.to_s] end # Returns a hash of attributes before typecasting and deserialization. diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 7f1ebab4cd..9caf73e627 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -50,20 +50,13 @@ module ActiveRecord end end - # *DEPRECATED*: Use ActiveRecord::AttributeMethods::Serialization::ClassMethods#serialized_attributes class level method instead. - def serialized_attributes - message = "Instance level serialized_attributes method is deprecated, please use class level method." - ActiveSupport::Deprecation.warn message - defined?(@serialized_attributes) ? @serialized_attributes : self.class.serialized_attributes - end - class Type # :nodoc: def initialize(column) @column = column end def type_cast(value) - value.unserialized_value + value.unserialized_value @column.type_cast value.value end def type @@ -72,17 +65,17 @@ module ActiveRecord end class Attribute < Struct.new(:coder, :value, :state) # :nodoc: - def unserialized_value - state == :serialized ? unserialize : value + def unserialized_value(v = value) + state == :serialized ? unserialize(v) : value end def serialized_value state == :unserialized ? serialize : value end - def unserialize + def unserialize(v) self.state = :unserialized - self.value = coder.load(value) + self.value = coder.load(v) end def serialize diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 87d4daa6d9..a8a1847554 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -301,7 +301,7 @@ module ActiveRecord def association_valid?(reflection, record) return true if record.destroyed? || record.marked_for_destruction? - unless valid = record.valid?(validation_context) + unless valid = record.valid? if reflection.options[:autosave] record.errors.each do |attribute, message| attribute = "#{reflection.name}.#{attribute}" diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 566550cbe2..c982d65d65 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -65,8 +65,7 @@ module ActiveRecord # Appends a primary key definition to the table definition. # Can be called multiple times, but this is probably not a good idea. def primary_key(name, type = :primary_key, options = {}) - options[:primary_key] = true - column(name, type, options) + column(name, type, options.merge(:primary_key => true)) end # Returns a ColumnDefinition for the column with name +name+. @@ -270,6 +269,7 @@ module ActiveRecord end column.limit = limit + column.array = options[:array] if column.respond_to?(:array) column.precision = options[:precision] column.scale = options[:scale] column.default = options[:default] diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 6e1f43cce6..2a4eda3622 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -606,7 +606,7 @@ module ActiveRecord index_options = options.delete(:index) add_column(table_name, "#{ref_name}_id", :integer, options) add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic - add_index(table_name, polymorphic ? %w[id type].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options + add_index(table_name, polymorphic ? %w[id type].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options end alias :add_belongs_to :add_reference @@ -710,6 +710,7 @@ module ActiveRecord # distinct("posts.id", ["posts.created_at desc"]) # def distinct(columns, order_by) + ActiveSupport::Deprecation.warn("#distinct is deprecated and shall be removed from future releases.") "DISTINCT #{columns_for_distinct(columns, order_by)}" end @@ -718,7 +719,7 @@ module ActiveRecord # require the order columns appear in the SELECT. # # columns_for_distinct("posts.id", ["posts.created_at desc"]) - def columns_for_distinct(columns, orders) + def columns_for_distinct(columns, orders) # :nodoc: columns end @@ -774,37 +775,23 @@ module ActiveRecord column_names = Array(column_name) index_name = index_name(table_name, column: column_names) - if Hash === options # legacy support, since this param was a string - options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type) + options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type) - index_type = options[:unique] ? "UNIQUE" : "" - index_type = options[:type].to_s if options.key?(:type) - index_name = options[:name].to_s if options.key?(:name) - max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length + index_type = options[:unique] ? "UNIQUE" : "" + index_type = options[:type].to_s if options.key?(:type) + index_name = options[:name].to_s if options.key?(:name) + max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length - if options.key?(:algorithm) - algorithm = index_algorithms.fetch(options[:algorithm]) { - raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}") - } - end - - using = "USING #{options[:using]}" if options[:using].present? - - if supports_partial_index? - index_options = options[:where] ? " WHERE #{options[:where]}" : "" - end - else - if options - message = "Passing a string as third argument of `add_index` is deprecated and will" + - " be removed in Rails 4.1." + - " Use add_index(#{table_name.inspect}, #{column_name.inspect}, unique: true) instead" + if options.key?(:algorithm) + algorithm = index_algorithms.fetch(options[:algorithm]) { + raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}") + } + end - ActiveSupport::Deprecation.warn message - end + using = "USING #{options[:using]}" if options[:using].present? - index_type = options - max_index_length = allowed_index_name_length - algorithm = using = nil + if supports_partial_index? + index_options = options[:where] ? " WHERE #{options[:where]}" : "" end if index_name.length > max_index_length diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index f23521430d..1826d88500 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -393,6 +393,14 @@ module ActiveRecord TYPES[new] = TYPES[old] end + def self.find_type(field) + if field.type == Mysql::Field::TYPE_TINY && field.length > 1 + TYPES[Mysql::Field::TYPE_LONG] + else + TYPES.fetch(field.type) { Fields::Identity.new } + end + end + register_type Mysql::Field::TYPE_TINY, Fields::Boolean.new register_type Mysql::Field::TYPE_LONG, Fields::Integer.new alias_type Mysql::Field::TYPE_LONGLONG, Mysql::Field::TYPE_LONG @@ -425,9 +433,7 @@ module ActiveRecord if field.decimals > 0 types[field.name] = Fields::Decimal.new else - types[field.name] = Fields::TYPES.fetch(field.type) { - Fields::Identity.new - } + types[field.name] = Fields.find_type field end } result_set = ActiveRecord::Result.new(types.keys, result.to_a, types) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb index a9ef11aa83..a73f0ac57f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb @@ -60,7 +60,7 @@ module ActiveRecord end def json_to_string(object) - if Hash === object + if Hash === object || Array === object ActiveSupport::JSON.encode(object) else object diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 40a3b82839..e9daa5d7ff 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -30,6 +30,7 @@ module ActiveRecord when Array case sql_type when 'point' then super(PostgreSQLColumn.point_to_string(value)) + when 'json' then super(PostgreSQLColumn.json_to_string(value)) else if column.array "'#{PostgreSQLColumn.array_to_string(value, column, self).gsub(/'/, "''")}'" @@ -98,6 +99,7 @@ module ActiveRecord when Array case column.sql_type when 'point' then PostgreSQLColumn.point_to_string(value) + when 'json' then PostgreSQLColumn.json_to_string(value) else return super(value, column) unless column.array PostgreSQLColumn.array_to_string(value, column, self) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 88b09e7999..d5a603cadc 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -622,9 +622,9 @@ module ActiveRecord true end - # Returns true if pg > 9.2 + # Returns true if pg > 9.1 def supports_extensions? - postgresql_version >= 90200 + postgresql_version >= 90100 end # Range datatypes weren't introduced until PostgreSQL 9.2 @@ -646,9 +646,9 @@ module ActiveRecord def extension_enabled?(name) if supports_extensions? - res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL)", + res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled", 'SCHEMA' - res.column_types['exists'].type_cast res.rows.first.first + res.column_types['enabled'].type_cast res.rows.first.first end end diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 34bfad2d62..7e38719811 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -155,6 +155,15 @@ module ActiveRecord # Raised when unknown attributes are supplied via mass assignment. class UnknownAttributeError < NoMethodError + + attr_reader :record, :attribute + + def initialize(record, attribute) + @record = record + @attribute = attribute.to_s + super("unknown attribute: #{attribute}") + end + end # Raised when an error occurred while doing a mass assignment to an attribute through the diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 45dc26f0ed..33e19313a0 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -567,7 +567,7 @@ module ActiveRecord # interpolate the fixture label row.each do |key, value| - row[key] = label if value == "$LABEL" + row[key] = label if "$LABEL" == value end # generate a primary key if necessary diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index 8df76c7f5f..40976bc29e 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -116,9 +116,10 @@ module ActiveRecord begin constant = ActiveSupport::Dependencies.constantize(candidate) return constant if candidate == constant.to_s - rescue NameError => e - # We don't want to swallow NoMethodError < NameError errors - raise e unless e.instance_of?(NameError) + # We don't want to swallow NoMethodError < NameError errors + rescue NoMethodError + raise + rescue NameError end end diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb index 8e4ddcac82..ddf2afca0c 100644 --- a/activerecord/lib/active_record/locking/pessimistic.rb +++ b/activerecord/lib/active_record/locking/pessimistic.rb @@ -64,7 +64,7 @@ module ActiveRecord end # Wraps the passed block in a transaction, locking the object - # before yielding. You pass can the SQL locking clause + # before yielding. You can pass the SQL locking clause # as argument (see <tt>lock!</tt>). def with_lock(lock = true) transaction do diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 6c020e1d57..e96c347f6f 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -357,11 +357,14 @@ module ActiveRecord class CheckPending def initialize(app) @app = app + @last_check = 0 end def call(env) - ActiveRecord::Base.logger.silence do + mtime = ActiveRecord::Migrator.last_migration.mtime.to_i + if @last_check < mtime ActiveRecord::Migration.check_pending! + @last_check = mtime end @app.call(env) end @@ -668,6 +671,7 @@ module ActiveRecord copied end + # Determines the version number of the next migration. def next_migration_number(number) if ActiveRecord::Base.timestamped_migrations [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max @@ -699,6 +703,10 @@ module ActiveRecord File.basename(filename) end + def mtime + File.mtime filename + end + delegate :migrate, :announce, :write, :disable_ddl_transaction, to: :migration private @@ -714,6 +722,16 @@ module ActiveRecord end + class NullMigration < MigrationProxy #:nodoc: + def initialize + super(nil, 0, nil, nil) + end + + def mtime + 0 + end + end + class Migrator#:nodoc: class << self attr_writer :migrations_paths @@ -784,7 +802,11 @@ module ActiveRecord end def last_version - migrations(migrations_paths).last.try(:version)||0 + last_migration.version + end + + def last_migration #:nodoc: + migrations(migrations_paths).last || NullMigration.new end def proper_table_name(name) diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index a8905ed739..582006ea7d 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -333,10 +333,44 @@ module ActiveRecord toggle(attribute).update_attribute(attribute, self[attribute]) end - # Reloads the attributes of this object from the database. - # The optional options argument is passed to find when reloading so you - # may do e.g. record.reload(lock: true) to reload the same record with - # an exclusive row lock. + # Reloads the record from the database. + # + # This method modifies the receiver in-place. Attributes are updated, and + # caches busted, in particular the associations cache. + # + # If the record no longer exists in the database <tt>ActiveRecord::RecordNotFound</tt> + # is raised. Otherwise, in addition to the in-place modification the method + # returns +self+ for convenience. + # + # The optional <tt>:lock</tt> flag option allows you to lock the reloaded record: + # + # reload(lock: true) # reload with pessimistic locking + # + # Reloading is commonly used in test suites to test something is actually + # written to the database, or when some action modifies the corresponding + # row in the database but not the object in memory: + # + # assert account.deposit!(25) + # assert_equal 25, account.credit # check it is updated in memory + # assert_equal 25, account.reload.credit # check it is also persisted + # + # Another commom use case is optimistic locking handling: + # + # def with_optimistic_retry + # begin + # yield + # rescue ActiveRecord::StaleObjectError + # begin + # # Reload lock_version in particular. + # reload + # rescue ActiveRecord::RecordNotFound + # # If the record is gone there is nothing to do. + # else + # retry + # end + # end + # end + # def reload(options = nil) clear_aggregation_cache clear_association_cache diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index f78ccb01aa..3d85898c41 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -14,7 +14,7 @@ module ActiveRecord # Executes a custom SQL query against your database and returns all the results. The results will # be returned as an array with columns requested encapsulated as attributes of the model you call # this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in - # a Product object with the attributes you specified in the SQL query. + # a +Product+ object with the attributes you specified in the SQL query. # # If you call a complicated SQL query which spans multiple tables the columns specified by the # SELECT will be attributes of the model, whether or not they are columns of the corresponding @@ -29,9 +29,10 @@ module ActiveRecord # Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id" # # => [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...] # - # # You can use the same string replacement techniques as you can with ActiveRecord#find + # You can use the same string replacement techniques as you can with <tt>ActiveRecord::QueryMethods#where</tt>: + # # Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date] - # # => [#<Post:0x36bff9c @attributes={"title"=>"The Cheap Man Buys Twice"}>, ...] + # Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }] def find_by_sql(sql, binds = []) result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds) column_types = {} diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index e36888d4a8..31a0ace864 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -44,7 +44,7 @@ module ActiveRecord ActiveRecord::Tasks::DatabaseTasks.migrations_paths = Rails.application.paths['db/migrate'].to_a ActiveRecord::Tasks::DatabaseTasks.fixtures_path = File.join Rails.root, 'test', 'fixtures' - if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH) + if defined?(APP_RAKEFILE) && engine = Rails::Engine.find(find_engine_path(APP_RAKEFILE)) if engine.paths['db/migrate'].existent ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths['db/migrate'].to_a end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 434af3c5f8..f69e9b2217 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -360,7 +360,7 @@ db_namespace = namespace :db do end # desc 'Check for pending migrations and load the test schema' - task :prepare do + task :prepare => :load_config do unless ActiveRecord::Base.configurations.blank? db_namespace['test:load'].invoke end diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb index 8499bb16e7..b3ddfd63d4 100644 --- a/activerecord/lib/active_record/readonly_attributes.rb +++ b/activerecord/lib/active_record/readonly_attributes.rb @@ -20,11 +20,5 @@ module ActiveRecord self._attr_readonly end end - - def _attr_readonly - message = "Instance level _attr_readonly method is deprecated, please use class level method." - ActiveSupport::Deprecation.warn message - defined?(@_attr_readonly) ? @_attr_readonly : self.class._attr_readonly - end end end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 0ba860a186..27aa20b6c0 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -8,7 +8,7 @@ module ActiveRecord self.reflections = {} end - # Reflection enables to interrogate Active Record classes and objects + # \Reflection enables to interrogate Active Record classes and objects # about their associations and aggregations. This information can, # for example, be used in a form builder that takes an Active Record object # and creates input fields for all of the attributes depending on their type @@ -21,11 +21,12 @@ module ActiveRecord case macro when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many klass = options[:through] ? ThroughReflection : AssociationReflection - reflection = klass.new(macro, name, scope, options, active_record) when :composed_of - reflection = AggregateReflection.new(macro, name, scope, options, active_record) + klass = AggregateReflection end + reflection = klass.new(macro, name, scope, options, active_record) + self.reflections = self.reflections.merge(name => reflection) reflection end @@ -100,7 +101,7 @@ module ActiveRecord # Returns the hash of options used for the macro. # # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt> - # <tt>has_many :clients</tt> returns +{}+ + # <tt>has_many :clients</tt> returns <tt>{}</tt> attr_reader :options attr_reader :active_record @@ -449,8 +450,8 @@ module ActiveRecord # Checks to see if the reflection doesn't have any options that prevent # us from being able to guess the inverse automatically. First, the # +automatic_inverse_of+ option cannot be set to false. Second, we must - # have :has_many, :has_one, :belongs_to associations. Third, we must - # not have options such as :class_name or :polymorphic which prevent us + # have +has_many+, +has_one+, +belongs_to+ associations. Third, we must + # not have options such as +:polymorphic+ or +:foreign_key+ which prevent us # from correctly guessing the inverse association. # # Anything with a scope can additionally ruin our attempt at finding an @@ -493,7 +494,7 @@ module ActiveRecord delegate :foreign_key, :foreign_type, :association_foreign_key, :active_record_primary_key, :type, :to => :source_reflection - # Gets the source of the through reflection. It checks both a singularized + # Returns the source of the through reflection. It checks both a singularized # and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>. # # class Post < ActiveRecord::Base @@ -507,8 +508,7 @@ module ActiveRecord # end # # tags_reflection = Post.reflect_on_association(:tags) - # - # taggings_reflection = tags_reflection.source_reflection + # tags_reflection.source_reflection # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags"> # def source_reflection @@ -524,7 +524,8 @@ module ActiveRecord # end # # tags_reflection = Post.reflect_on_association(:tags) - # taggings_reflection = tags_reflection.through_reflection + # tags_reflection.through_reflection + # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @active_record=Post, @plural_name="taggings"> # def through_reflection @through_reflection ||= active_record.reflect_on_association(options[:through]) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index ae3fa85da9..d54479edbb 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -26,7 +26,6 @@ module ActiveRecord @klass = klass @table = table @values = values - @implicit_readonly = nil @loaded = false @default_scoped = false end @@ -76,10 +75,10 @@ module ActiveRecord def update_record(values, id, id_was) # :nodoc: substitutes, binds = substitute_values values um = @klass.unscoped.where(@klass.arel_table[@klass.primary_key].eq(id_was || id)).arel.compile_update(substitutes) - + @klass.connection.update( - um, - 'SQL', + um, + 'SQL', binds) end @@ -94,7 +93,7 @@ module ActiveRecord end [substitutes, binds] - end + end # Initializes new record from relation while maintaining the current # scope. @@ -155,34 +154,58 @@ module ActiveRecord first || new(attributes, &block) end - # Finds the first record with the given attributes, or creates a record with the attributes - # if one is not found. + # Finds the first record with the given attributes, or creates a record + # with the attributes if one is not found: # - # ==== Examples - # # Find the first user named Penélope or create a new one. + # # Find the first user named "Penélope" or create a new one. # User.find_or_create_by(first_name: 'Penélope') - # # => <User id: 1, first_name: 'Penélope', last_name: nil> + # # => #<User id: 1, first_name: "Penélope", last_name: nil> # - # # Find the first user named Penélope or create a new one. + # # Find the first user named "Penélope" or create a new one. # # We already have one so the existing record will be returned. # User.find_or_create_by(first_name: 'Penélope') - # # => <User id: 1, first_name: 'Penélope', last_name: nil> + # # => #<User id: 1, first_name: "Penélope", last_name: nil> # - # # Find the first user named Scarlett or create a new one with a particular last name. + # # Find the first user named "Scarlett" or create a new one with + # # a particular last name. # User.create_with(last_name: 'Johansson').find_or_create_by(first_name: 'Scarlett') - # # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'> + # # => #<User id: 2, first_name: "Scarlett", last_name: "Johansson"> # - # # Find the first user named Scarlett or create a new one with a different last name. - # # We already have one so the existing record will be returned. + # This method accepts a block, which is passed down to +create+. The last example + # above can be alternatively written this way: + # + # # Find the first user named "Scarlett" or create a new one with a + # # different last name. # User.find_or_create_by(first_name: 'Scarlett') do |user| - # user.last_name = "O'Hara" + # user.last_name = 'Johansson' # end - # # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'> + # # => #<User id: 2, first_name: "Scarlett", last_name: "Johansson"> + # + # This method always returns a record, but if creation was attempted and + # failed due to validation errors it won't be persisted, you get what + # +create+ returns in such situation. + # + # Please note *this method is not atomic*, it runs first a SELECT, and if + # there are no results an INSERT is attempted. If there are other threads + # or processes there is a race condition between both calls and it could + # be the case that you end up with two similar records. + # + # Whether that is a problem or not depends on the logic of the + # application, but in the particular case in which rows have a UNIQUE + # constraint an exception may be raised, just retry: + # + # begin + # CreditAccount.find_or_create_by(user_id: user.id) + # rescue ActiveRecord::RecordNotUnique + # retry + # end + # def find_or_create_by(attributes, &block) find_by(attributes) || create(attributes, &block) end - # Like <tt>find_or_create_by</tt>, but calls <tt>create!</tt> so an exception is raised if the created record is invalid. + # Like <tt>find_or_create_by</tt>, but calls <tt>create!</tt> so an exception + # is raised if the created record is invalid. def find_or_create_by!(attributes, &block) find_by(attributes) || create!(attributes, &block) end @@ -489,11 +512,13 @@ module ActiveRecord # User.where(name: 'Oscar').where_values_hash # # => {name: "Oscar"} def where_values_hash - equalities = with_default_scope.where_values.grep(Arel::Nodes::Equality).find_all { |node| + scope = with_default_scope + equalities = scope.where_values.grep(Arel::Nodes::Equality).find_all { |node| node.left.relation.name == table_name } - binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }] + binds = Hash[scope.bind_values.find_all(&:first).map { |column, v| [column.name, v] }] + binds.merge!(Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]) Hash[equalities.map { |where| name = where.left.name @@ -580,10 +605,7 @@ module ActiveRecord ActiveRecord::Associations::Preloader.new(@records, associations).run end - # @readonly_value is true only if set explicitly. @implicit_readonly is true if there - # are JOINS and no explicit SELECT. - readonly = readonly_value.nil? ? @implicit_readonly : readonly_value - @records.each { |record| record.readonly! } if readonly + @records.each { |record| record.readonly! } if readonly_value else @records = default_scoped.to_a end diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 7371fe370e..ccb48247b7 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -56,15 +56,7 @@ module ActiveRecord # # Person.sum(:age) # => 4562 def sum(*args) - if block_given? - ActiveSupport::Deprecation.warn( - "Calling #sum with a block is deprecated and will be removed in Rails 4.1. " \ - "If you want to perform sum calculation over the array of elements, use `to_a.sum(&block)`." - ) - self.to_a.sum(*args) {|*block_args| yield(*block_args)} - else - calculate(:sum, *args) - end + calculate(:sum, *args) end # This calculates aggregate values in the given column. Methods for count, sum, average, diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index eea43aff0e..c114ea0c0d 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -99,8 +99,15 @@ module ActiveRecord end def merge_multi_values - relation.where_values = merged_wheres - relation.bind_values = merged_binds + lhs_wheres = relation.where_values + rhs_wheres = values[:where] || [] + lhs_binds = relation.bind_values + rhs_binds = values[:bind] || [] + + removed, kept = partition_overwrites(lhs_wheres, rhs_wheres) + + relation.where_values = kept + rhs_wheres + relation.bind_values = filter_binds(lhs_binds, removed) + rhs_binds if values[:reordering] # override any order specified in the original relation @@ -123,33 +130,27 @@ module ActiveRecord end end - def merged_binds - if values[:bind] - (relation.bind_values + values[:bind]).uniq(&:first) - else - relation.bind_values - end - end - - def merged_wheres - values[:where] ||= [] + def filter_binds(lhs_binds, removed_wheres) + return lhs_binds if removed_wheres.empty? - if values[:where].empty? || relation.where_values.empty? - relation.where_values + values[:where] - else - sanitized_wheres + values[:where] - end + set = Set.new removed_wheres.map { |x| x.left.name } + lhs_binds.dup.delete_if { |col,_| set.include? col.name } end # Remove equalities from the existing relation with a LHS which is # present in the relation being merged in. - def sanitized_wheres - seen = Set.new - values[:where].each do |w| - seen << w.left if w.respond_to?(:operator) && w.operator == :== + # returns [things_to_remove, things_to_keep] + def partition_overwrites(lhs_wheres, rhs_wheres) + if lhs_wheres.empty? || rhs_wheres.empty? + return [[], lhs_wheres] + end + + nodes = rhs_wheres.find_all do |w| + w.respond_to?(:operator) && w.operator == :== end + seen = Set.new(nodes) { |node| node.left } - relation.where_values.reject do |w| + lhs_wheres.partition do |w| w.respond_to?(:operator) && w.operator == :== && seen.include?(w.left) end end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 9fcd2d06c5..ca1de2d4dc 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -340,6 +340,9 @@ module ActiveRecord # User.where(name: "John", active: true).unscope(where: :name) # == User.where(active: true) # + # This method is applied before the default_scope is applied. So the conditions + # specified in default_scope will not be removed. + # # Note that this method is more generalized than ActiveRecord::SpawnMethods#except # because #except will only affect a particular relation's values. It won't wipe # the order, grouping, etc. when that relation is merged. For example: @@ -861,8 +864,6 @@ module ActiveRecord return [] if joins.empty? - @implicit_readonly = true - joins.map do |join| case join when Array @@ -944,8 +945,6 @@ module ActiveRecord join_dependency.graft(*stashed_association_joins) - @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty? - # FIXME: refactor this to build an AST join_dependency.join_associations.each do |association| association.join_to(manager) @@ -958,7 +957,6 @@ module ActiveRecord def build_select(arel, selects) unless selects.empty? - @implicit_readonly = false arel.project(*selects) else arel.project(@klass.arel_table[Arel.star]) @@ -1012,7 +1010,7 @@ module ActiveRecord end def validate_order_args(args) - args.select { |a| Hash === a }.each do |h| + args.grep(Hash) do |h| unless (h.values - [:asc, :desc]).empty? raise ArgumentError, 'Direction should be :asc or :desc' end diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb index 3259dbbd80..4bfd0167a4 100644 --- a/activerecord/lib/active_record/schema.rb +++ b/activerecord/lib/active_record/schema.rb @@ -43,7 +43,7 @@ module ActiveRecord unless info[:version].blank? initialize_schema_migrations_table - assume_migrated_upto_version(info[:version], migrations_paths) + connection.assume_migrated_upto_version(info[:version], migrations_paths) end end diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb index 6077144265..fee19b1096 100644 --- a/activerecord/lib/active_record/schema_migration.rb +++ b/activerecord/lib/active_record/schema_migration.rb @@ -4,31 +4,37 @@ require 'active_record/base' module ActiveRecord class SchemaMigration < ActiveRecord::Base + class << self - def self.table_name - "#{Base.table_name_prefix}schema_migrations#{Base.table_name_suffix}" - end + def table_name + "#{table_name_prefix}schema_migrations#{table_name_suffix}" + end - def self.index_name - "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}" - end + def index_name + "#{table_name_prefix}unique_schema_migrations#{table_name_suffix}" + end - def self.create_table(limit=nil) - unless connection.table_exists?(table_name) - version_options = {null: false} - version_options[:limit] = limit if limit + def table_exists? + connection.table_exists?(table_name) + end + + def create_table(limit=nil) + unless table_exists? + version_options = {null: false} + version_options[:limit] = limit if limit - connection.create_table(table_name, id: false) do |t| - t.column :version, :string, version_options + connection.create_table(table_name, id: false) do |t| + t.column :version, :string, version_options + end + connection.add_index table_name, :version, unique: true, name: index_name end - connection.add_index table_name, :version, unique: true, name: index_name end - end - def self.drop_table - if connection.table_exists?(table_name) - connection.remove_index table_name, name: index_name - connection.drop_table(table_name) + def drop_table + if table_exists? + connection.remove_index table_name, name: index_name + connection.drop_table(table_name) + end end end diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb index e9142481a3..1b4c473bfc 100644 --- a/activerecord/lib/active_record/test_case.rb +++ b/activerecord/lib/active_record/test_case.rb @@ -35,8 +35,7 @@ module ActiveRecord def assert_queries(num = 1, options = {}) ignore_none = options.fetch(:ignore_none) { num == :any } SQLCounter.clear_log - yield - ensure + x = yield the_log = ignore_none ? SQLCounter.log_all : SQLCounter.log if num == :any assert_operator the_log.size, :>=, 1, "1 or more queries expected, but none were executed." @@ -44,6 +43,7 @@ module ActiveRecord mesg = "#{the_log.size} instead of #{num} queries were executed.#{the_log.size == 0 ? '' : "\nQueries:\n#{the_log.join("\n")}"}" assert_equal num, the_log.size, mesg end + x end def assert_no_queries(&block) diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index a5955ccba4..77634b40bb 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -10,7 +10,9 @@ module ActiveRecord end included do - define_callbacks :commit, :rollback, :terminator => "result == false", :scope => [:kind, :name] + define_callbacks :commit, :rollback, + terminator: ->(_, result) { result == false }, + scope: [:kind, :name] end # = Active Record Transactions diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb index 744780d069..b4785d3ba4 100644 --- a/activerecord/lib/active_record/validations/associated.rb +++ b/activerecord/lib/active_record/validations/associated.rb @@ -2,7 +2,7 @@ module ActiveRecord module Validations class AssociatedValidator < ActiveModel::EachValidator #:nodoc: def validate_each(record, attribute, value) - if Array.wrap(value).reject {|r| r.marked_for_destruction? || r.valid?(record.validation_context) }.any? + if Array.wrap(value).reject {|r| r.marked_for_destruction? || r.valid?}.any? record.errors.add(attribute, :invalid, options.merge(:value => value)) end end diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index a705d8c2c4..52e46e1ffe 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -7,11 +7,7 @@ module ActiveRecord "Pass a callable instead: `conditions: -> { where(approved: true) }`" end super({ case_sensitive: true }.merge!(options)) - end - - # Unfortunately, we have to tie Uniqueness validators to a class. - def setup(klass) - @klass = klass + @klass = options[:class] end def validate_each(record, attribute, value) @@ -34,7 +30,6 @@ module ActiveRecord end protected - # The check for an existing value should be run from a class that # isn't abstract. This means working down from the current class # (self), to the first non-abstract class. Since classes don't know diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb index b967bb6e0f..3968acba64 100644 --- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb +++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb @@ -14,6 +14,10 @@ module ActiveRecord protected attr_reader :migration_action, :join_tables + # sets the default migration template that is being used for the generation of the migration + # depending on the arguments which would be sent out in the command line, the migration template + # and the table name instance variables are setup. + def set_local_assigns! @migration_template = "migration.rb" case file_name diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb index 40e134e626..7e8d68ce69 100644 --- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb +++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb @@ -12,6 +12,9 @@ module ActiveRecord class_option :parent, :type => :string, :desc => "The parent class for the generated model" class_option :indexes, :type => :boolean, :default => true, :desc => "Add indexes for references and belongs_to columns" + + # creates the migration file for the model. + def create_migration_file return unless options[:migration] && options[:parent].nil? attributes.each { |a| a.attr_options.delete(:index) if a.reference? && !a.has_index? } if options[:indexes] == false @@ -39,6 +42,7 @@ module ActiveRecord protected + # Used by the migration template to determine the parent name of the model def parent_class_name options[:parent] || "ActiveRecord::Base" end |