diff options
author | Xavier Noria <fxn@hashref.com> | 2016-08-06 19:55:02 +0200 |
---|---|---|
committer | Xavier Noria <fxn@hashref.com> | 2016-08-06 20:16:27 +0200 |
commit | 80e66cc4d90bf8c15d1a5f6e3152e90147f00772 (patch) | |
tree | e7e75464af04f3cf1935b29238dbd7cb2337b0dd /activerecord/lib | |
parent | 411ccbdab2608c62aabdb320d52cb02d446bb39c (diff) | |
download | rails-80e66cc4d90bf8c15d1a5f6e3152e90147f00772.tar.gz rails-80e66cc4d90bf8c15d1a5f6e3152e90147f00772.tar.bz2 rails-80e66cc4d90bf8c15d1a5f6e3152e90147f00772.zip |
normalizes indentation and whitespace across the project
Diffstat (limited to 'activerecord/lib')
114 files changed, 4609 insertions, 4610 deletions
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index 9f965052c5..55076c4314 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -179,104 +179,104 @@ module ActiveRecord # # Customer.where(balance: Money.new(20, "USD")) # - module ClassMethods - # Adds reader and writer methods for manipulating a value object: - # <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods. - # - # Options are: - # * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name - # can't be inferred from the part id. So <tt>composed_of :address</tt> will by default be linked - # to the Address class, but if the real class name is +CompanyAddress+, you'll have to specify it - # with this option. - # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value - # object. Each mapping is represented as an array where the first item is the name of the - # entity attribute and the second item is the name of the attribute in the value object. The - # order in which mappings are defined determines the order in which attributes are sent to the - # value class constructor. - # * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped - # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all - # mapped attributes. - # This defaults to +false+. - # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that - # is called to initialize the value object. The constructor is passed all of the mapped attributes, - # in the order that they are defined in the <tt>:mapping option</tt>, as arguments and uses them - # to instantiate a <tt>:class_name</tt> object. - # The default is <tt>:new</tt>. - # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt> - # or a Proc that is called when a new value is assigned to the value object. The converter is - # passed the single value that is used in the assignment and is only called if the new value is - # not an instance of <tt>:class_name</tt>. If <tt>:allow_nil</tt> is set to true, the converter - # can return nil to skip the assignment. - # - # Option examples: - # composed_of :temperature, mapping: %w(reading celsius) - # composed_of :balance, class_name: "Money", mapping: %w(balance amount), - # converter: Proc.new { |balance| balance.to_money } - # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ] - # composed_of :gps_location - # composed_of :gps_location, allow_nil: true - # composed_of :ip_address, - # class_name: 'IPAddr', - # mapping: %w(ip to_i), - # constructor: Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) }, - # converter: Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) } - # - def composed_of(part_id, options = {}) - options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter) + module ClassMethods + # Adds reader and writer methods for manipulating a value object: + # <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods. + # + # Options are: + # * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name + # can't be inferred from the part id. So <tt>composed_of :address</tt> will by default be linked + # to the Address class, but if the real class name is +CompanyAddress+, you'll have to specify it + # with this option. + # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value + # object. Each mapping is represented as an array where the first item is the name of the + # entity attribute and the second item is the name of the attribute in the value object. The + # order in which mappings are defined determines the order in which attributes are sent to the + # value class constructor. + # * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped + # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all + # mapped attributes. + # This defaults to +false+. + # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that + # is called to initialize the value object. The constructor is passed all of the mapped attributes, + # in the order that they are defined in the <tt>:mapping option</tt>, as arguments and uses them + # to instantiate a <tt>:class_name</tt> object. + # The default is <tt>:new</tt>. + # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt> + # or a Proc that is called when a new value is assigned to the value object. The converter is + # passed the single value that is used in the assignment and is only called if the new value is + # not an instance of <tt>:class_name</tt>. If <tt>:allow_nil</tt> is set to true, the converter + # can return nil to skip the assignment. + # + # Option examples: + # composed_of :temperature, mapping: %w(reading celsius) + # composed_of :balance, class_name: "Money", mapping: %w(balance amount), + # converter: Proc.new { |balance| balance.to_money } + # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ] + # composed_of :gps_location + # composed_of :gps_location, allow_nil: true + # composed_of :ip_address, + # class_name: 'IPAddr', + # mapping: %w(ip to_i), + # constructor: Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) }, + # converter: Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) } + # + def composed_of(part_id, options = {}) + options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter) - name = part_id.id2name - class_name = options[:class_name] || name.camelize - mapping = options[:mapping] || [ name, name ] - mapping = [ mapping ] unless mapping.first.is_a?(Array) - allow_nil = options[:allow_nil] || false - constructor = options[:constructor] || :new - converter = options[:converter] + name = part_id.id2name + class_name = options[:class_name] || name.camelize + mapping = options[:mapping] || [ name, name ] + mapping = [ mapping ] unless mapping.first.is_a?(Array) + allow_nil = options[:allow_nil] || false + constructor = options[:constructor] || :new + converter = options[:converter] - reader_method(name, class_name, mapping, allow_nil, constructor) - writer_method(name, class_name, mapping, allow_nil, converter) + reader_method(name, class_name, mapping, allow_nil, constructor) + writer_method(name, class_name, mapping, allow_nil, converter) - reflection = ActiveRecord::Reflection.create(:composed_of, part_id, nil, options, self) - Reflection.add_aggregate_reflection self, part_id, reflection - end + reflection = ActiveRecord::Reflection.create(:composed_of, part_id, nil, options, self) + Reflection.add_aggregate_reflection self, part_id, reflection + end - private - def reader_method(name, class_name, mapping, allow_nil, constructor) - define_method(name) do - if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|key, _| !_read_attribute(key).nil? }) - attrs = mapping.collect {|key, _| _read_attribute(key)} - object = constructor.respond_to?(:call) ? - constructor.call(*attrs) : - class_name.constantize.send(constructor, *attrs) - @aggregation_cache[name] = object + private + def reader_method(name, class_name, mapping, allow_nil, constructor) + define_method(name) do + if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|key, _| !_read_attribute(key).nil? }) + attrs = mapping.collect {|key, _| _read_attribute(key)} + object = constructor.respond_to?(:call) ? + constructor.call(*attrs) : + class_name.constantize.send(constructor, *attrs) + @aggregation_cache[name] = object + end + @aggregation_cache[name] end - @aggregation_cache[name] end - end - def writer_method(name, class_name, mapping, allow_nil, converter) - define_method("#{name}=") do |part| - klass = class_name.constantize + def writer_method(name, class_name, mapping, allow_nil, converter) + define_method("#{name}=") do |part| + klass = class_name.constantize - unless part.is_a?(klass) || converter.nil? || part.nil? - part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part) - end + unless part.is_a?(klass) || converter.nil? || part.nil? + part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part) + end - hash_from_multiparameter_assignment = part.is_a?(Hash) && - part.each_key.all? { |k| k.is_a?(Integer) } - if hash_from_multiparameter_assignment - raise ArgumentError unless part.size == part.each_key.max - part = klass.new(*part.sort.map(&:last)) - end + hash_from_multiparameter_assignment = part.is_a?(Hash) && + part.each_key.all? { |k| k.is_a?(Integer) } + if hash_from_multiparameter_assignment + raise ArgumentError unless part.size == part.each_key.max + part = klass.new(*part.sort.map(&:last)) + end - if part.nil? && allow_nil - mapping.each { |key, _| self[key] = nil } - @aggregation_cache[name] = nil - else - mapping.each { |key, value| self[key] = part.send(value) } - @aggregation_cache[name] = part.freeze + if part.nil? && allow_nil + mapping.each { |key, _| self[key] = nil } + @aggregation_cache[name] = nil + else + mapping.each { |key, value| self[key] = part.send(value) } + @aggregation_cache[name] = part.freeze + end end end - end - end + end end end diff --git a/activerecord/lib/active_record/association_relation.rb b/activerecord/lib/active_record/association_relation.rb index c18e88e4cf..2da2d968b9 100644 --- a/activerecord/lib/active_record/association_relation.rb +++ b/activerecord/lib/active_record/association_relation.rb @@ -28,8 +28,8 @@ module ActiveRecord private - def exec_queries - super.each { |r| @association.set_inverse_instance r } - end + def exec_queries + super.each { |r| @association.set_inverse_instance r } + end end end diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index d1d71391e6..659e512ce2 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1150,684 +1150,684 @@ module ActiveRecord # # All of the association macros can be specialized through options. This makes cases # more complex than the simple and guessable ones possible. - module ClassMethods - # Specifies a one-to-many association. The following methods for retrieval and query of - # collections of associated objects will be added: - # - # +collection+ is a placeholder for the symbol passed as the +name+ argument, so - # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>. - # - # [collection(force_reload = false)] - # Returns an array of all the associated objects. - # 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 - # parent object, unless the parent object is a new record. - # This will also run validations and callbacks of associated object(s). - # [collection.delete(object, ...)] - # Removes one or more objects from the collection by setting their foreign keys to +NULL+. - # Objects will be in addition destroyed if they're associated with <tt>dependent: :destroy</tt>, - # and deleted if they're associated with <tt>dependent: :delete_all</tt>. - # - # If the <tt>:through</tt> option is used, then the join records are deleted (rather than - # nullified) by default, but you can specify <tt>dependent: :destroy</tt> or - # <tt>dependent: :nullify</tt> to override this. - # [collection.destroy(object, ...)] - # Removes one or more objects from the collection by running <tt>destroy</tt> on - # each record, regardless of any dependent option, ensuring callbacks are run. - # - # If the <tt>:through</tt> option is used, then the join records are destroyed - # instead, not the objects themselves. - # [collection=objects] - # Replaces the collections content by deleting and adding objects as appropriate. If the <tt>:through</tt> - # option is true callbacks in the join models are triggered except destroy callbacks, since deletion is - # direct by default. You can specify <tt>dependent: :destroy</tt> or - # <tt>dependent: :nullify</tt> to override this. - # [collection_singular_ids] - # Returns an array of the associated objects' ids - # [collection_singular_ids=ids] - # Replace the collection with the objects identified by the primary keys in +ids+. This - # method loads the models and calls <tt>collection=</tt>. See above. - # [collection.clear] - # Removes every object from the collection. This destroys the associated objects if they - # are associated with <tt>dependent: :destroy</tt>, deletes them directly from the - # database if <tt>dependent: :delete_all</tt>, otherwise sets their foreign keys to +NULL+. - # If the <tt>:through</tt> option is true no destroy callbacks are invoked on the join models. - # Join models are directly deleted. - # [collection.empty?] - # Returns +true+ if there are no associated objects. - # [collection.size] - # Returns the number of associated objects. - # [collection.find(...)] - # Finds an associated object according to the same rules as ActiveRecord::FinderMethods#find. - # [collection.exists?(...)] - # Checks whether an associated object with the given conditions exists. - # Uses the same rules as ActiveRecord::FinderMethods#exists?. - # [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 - # been saved. - # [collection.create(attributes = {})] - # Returns a new object of the collection type that has been instantiated - # with +attributes+, linked to this object through a foreign key, and that has already - # been saved (if it passed the validation). *Note*: This only works if the base model - # already exists in the DB, not if it is a new (unsaved) record! - # [collection.create!(attributes = {})] - # Does the same as <tt>collection.create</tt>, but raises ActiveRecord::RecordInvalid - # if the record is invalid. - # - # === Example - # - # 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> - # * <tt>Firm#clients.destroy</tt> - # * <tt>Firm#clients=</tt> - # * <tt>Firm#client_ids</tt> - # * <tt>Firm#client_ids=</tt> - # * <tt>Firm#clients.clear</tt> - # * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>) - # * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>) - # * <tt>Firm#clients.find</tt> (similar to <tt>Client.where(firm_id: id).find(id)</tt>) - # * <tt>Firm#clients.exists?(name: 'ACME')</tt> (similar to <tt>Client.exists?(name: 'ACME', firm_id: firm.id)</tt>) - # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>) - # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>) - # * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save!</tt>) - # The declaration can also include an +options+ hash to specialize the behavior of the association. - # - # === Scopes - # - # You can pass a second argument +scope+ as a callable (i.e. proc or - # lambda) to retrieve a specific set of records or customize the generated - # query when you access the associated collection. - # - # Scope examples: - # has_many :comments, -> { where(author_id: 1) } - # has_many :employees, -> { joins(:address) } - # has_many :posts, ->(post) { where("max_post_length > ?", post.length) } - # - # === Extensions - # - # The +extension+ argument allows you to pass a block into a has_many - # association. This is useful for adding new finders, creators and other - # factory-type methods to be used as part of the association. - # - # Extension examples: - # has_many :employees do - # def find_or_create_by_name(name) - # first_name, last_name = name.split(" ", 2) - # find_or_create_by(first_name: first_name, last_name: last_name) - # end - # end - # - # === Options - # [:class_name] - # Specify the class name of the association. Use it only if that name can't be inferred - # from the association name. So <tt>has_many :products</tt> will by default be linked - # to the +Product+ class, but if the real class name is +SpecialProduct+, you'll have to - # specify it with this option. - # [:foreign_key] - # Specify the foreign key used for the association. By default this is guessed to be the name - # of this class in lower-case and "_id" suffixed. So a Person class that makes a #has_many - # association will use "person_id" as the default <tt>:foreign_key</tt>. - # [:foreign_type] - # Specify the column used to store the associated object's type, if this is a polymorphic - # association. By default this is guessed to be the name of the polymorphic association - # specified on "as" option with a "_type" suffix. So a class that defines a - # <tt>has_many :tags, as: :taggable</tt> association will use "taggable_type" as the - # default <tt>:foreign_type</tt>. - # [:primary_key] - # Specify the name of the column to use as the primary key for the association. By default this is +id+. - # [:dependent] - # 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 <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). - # * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Callbacks are not executed. - # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records. - # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects. - # - # If using with the <tt>:through</tt> option, the association on the join model must be - # a #belongs_to, and the records which get deleted are the join records, rather than - # the associated records. - # [:counter_cache] - # This option can be used to configure a custom named <tt>:counter_cache.</tt> You only need this option, - # when you customized the name of your <tt>:counter_cache</tt> on the #belongs_to association. - # [:as] - # Specifies a polymorphic interface (See #belongs_to). - # [:through] - # Specifies an association through which to perform the query. This can be any other type - # of association, including other <tt>:through</tt> associations. Options for <tt>:class_name</tt>, - # <tt>:primary_key</tt> and <tt>:foreign_key</tt> are ignored, as the association uses the - # source reflection. - # - # If the association on the join model is a #belongs_to, the collection can be modified - # and the records on the <tt>:through</tt> model will be automatically created and removed - # as appropriate. Otherwise, the collection is read-only, so you should manipulate the - # <tt>:through</tt> association directly. - # - # If you are going to modify the association (rather than just read from it), then it is - # a good idea to set the <tt>:inverse_of</tt> option on the source association on the - # join model. This allows associated records to be built which will automatically create - # the appropriate join model records when they are saved. (See the 'Association Join Models' - # section above.) - # [:source] - # Specifies the source association name used by #has_many <tt>:through</tt> queries. - # Only use it if the name cannot be inferred from the association. - # <tt>has_many :subscribers, through: :subscriptions</tt> will look for either <tt>:subscribers</tt> or - # <tt>:subscriber</tt> on Subscription, unless a <tt>:source</tt> is given. - # [:source_type] - # Specifies type of the source association used by #has_many <tt>:through</tt> queries where the source - # association is a polymorphic #belongs_to. - # [:validate] - # When set to +true+, validates new objects added to association when saving the parent object. +true+ by default. - # If you want to ensure associated objects are revalidated on every update, use +validates_associated+. - # [:autosave] - # 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. - # - # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets - # <tt>:autosave</tt> to <tt>true</tt>. - # [:inverse_of] - # Specifies the name of the #belongs_to association on the associated object - # that is the inverse of this #has_many association. Does not work in combination - # with <tt>:through</tt> or <tt>:as</tt> options. - # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. - # [:extend] - # Specifies a module or array of modules that will be extended into the association object returned. - # Useful for defining methods on associations, especially when they should be shared between multiple - # association objects. - # - # Option examples: - # has_many :comments, -> { order("posted_on") } - # has_many :comments, -> { includes(:author) } - # has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person" - # has_many :tracks, -> { order("position") }, dependent: :destroy - # has_many :comments, dependent: :nullify - # has_many :tags, as: :taggable - # has_many :reports, -> { readonly } - # has_many :subscribers, through: :subscriptions, source: :user - def has_many(name, scope = nil, options = {}, &extension) - reflection = Builder::HasMany.build(self, name, scope, options, &extension) - Reflection.add_reflection self, name, reflection - end - - # 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. - # - # The following methods for retrieval and query of a single associated object will be added: - # - # +association+ is a placeholder for the symbol passed as the +name+ argument, so - # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>. - # - # [association(force_reload = false)] - # Returns the associated object. +nil+ is returned if none is found. - # [association=(associate)] - # Assigns the associate object, extracts the primary key, sets it as the foreign key, - # and saves the associate object. To avoid database inconsistencies, permanently deletes an existing - # associated object when assigning a new one, even if the new one isn't saved to database. - # [build_association(attributes = {})] - # Returns a new object of the associated type that has been instantiated - # with +attributes+ and linked to this object through a foreign key, but has not - # yet been saved. - # [create_association(attributes = {})] - # Returns a new object of the associated type that has been instantiated - # with +attributes+, linked to this object through a foreign key, and that - # has already been saved (if it passed the validation). - # [create_association!(attributes = {})] - # Does the same as <tt>create_association</tt>, but raises ActiveRecord::RecordInvalid - # if the record is invalid. - # - # === Example - # - # An Account class declares <tt>has_one :beneficiary</tt>, which will add: - # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.where(account_id: id).first</tt>) - # * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>) - # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>) - # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>) - # * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save!; b</tt>) - # - # === Scopes - # - # You can pass a second argument +scope+ as a callable (i.e. proc or - # lambda) to retrieve a specific record or customize the generated query - # when you access the associated object. - # - # Scope examples: - # has_one :author, -> { where(comment_id: 1) } - # has_one :employer, -> { joins(:company) } - # has_one :dob, ->(dob) { where("Date.new(2000, 01, 01) > ?", dob) } - # - # === Options - # - # The declaration can also include an +options+ hash to specialize the behavior of the association. - # - # Options are: - # [:class_name] - # Specify the class name of the association. Use it only if that name can't be inferred - # from the association name. So <tt>has_one :manager</tt> will by default be linked to the Manager class, but - # if the real class name is Person, you'll have to specify it with this option. - # [:dependent] - # Controls what happens to the associated object when - # its owner is destroyed: - # - # * <tt>:destroy</tt> causes the associated object to also be destroyed - # * <tt>:delete</tt> causes the associated object to be deleted directly from the database (so callbacks will not execute) - # * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Callbacks are not executed. - # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there is an associated record - # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there is an associated object - # - # Note that <tt>:dependent</tt> option is ignored when using <tt>:through</tt> option. - # [:foreign_key] - # Specify the foreign key used for the association. By default this is guessed to be the name - # of this class in lower-case and "_id" suffixed. So a Person class that makes a #has_one association - # will use "person_id" as the default <tt>:foreign_key</tt>. - # [:foreign_type] - # Specify the column used to store the associated object's type, if this is a polymorphic - # association. By default this is guessed to be the name of the polymorphic association - # specified on "as" option with a "_type" suffix. So a class that defines a - # <tt>has_one :tag, as: :taggable</tt> association will use "taggable_type" as the - # default <tt>:foreign_type</tt>. - # [:primary_key] - # Specify the method that returns the primary key used for the association. By default this is +id+. - # [:as] - # Specifies a polymorphic interface (See #belongs_to). - # [:through] - # Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt>, - # <tt>:primary_key</tt>, and <tt>:foreign_key</tt> are ignored, as the association uses the - # source reflection. You can only use a <tt>:through</tt> query through a #has_one - # or #belongs_to association on the join model. - # [:source] - # Specifies the source association name used by #has_one <tt>:through</tt> queries. - # Only use it if the name cannot be inferred from the association. - # <tt>has_one :favorite, through: :favorites</tt> will look for a - # <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given. - # [:source_type] - # Specifies type of the source association used by #has_one <tt>:through</tt> queries where the source - # association is a polymorphic #belongs_to. - # [:validate] - # When set to +true+, validates new objects added to association when saving the parent object. +false+ by default. - # If you want to ensure associated objects are revalidated on every update, use +validates_associated+. - # [:autosave] - # If true, always save the associated object or destroy it if marked for destruction, - # when saving the parent object. If false, never save or destroy the associated object. - # By default, only save the associated object if it's a new record. - # - # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets - # <tt>:autosave</tt> to <tt>true</tt>. - # [:inverse_of] - # Specifies the name of the #belongs_to association on the associated object - # that is the inverse of this #has_one association. Does not work in combination - # with <tt>:through</tt> or <tt>:as</tt> options. - # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. - # [:required] - # When set to +true+, the association will also have its presence validated. - # This will validate the association itself, not the id. You can use - # +:inverse_of+ to avoid an extra query during validation. - # - # Option examples: - # has_one :credit_card, dependent: :destroy # destroys the associated credit card - # has_one :credit_card, dependent: :nullify # updates the associated records foreign - # # key value to NULL rather than destroying it - # has_one :last_comment, -> { order('posted_on') }, class_name: "Comment" - # has_one :project_manager, -> { where(role: 'project_manager') }, class_name: "Person" - # has_one :attachment, as: :attachable - # has_one :boss, -> { readonly } - # has_one :club, through: :membership - # has_one :primary_address, -> { where(primary: true) }, through: :addressables, source: :addressable - # has_one :credit_card, required: true - def has_one(name, scope = nil, options = {}) - reflection = Builder::HasOne.build(self, name, scope, options) - Reflection.add_reflection self, name, reflection - end + module ClassMethods + # Specifies a one-to-many association. The following methods for retrieval and query of + # collections of associated objects will be added: + # + # +collection+ is a placeholder for the symbol passed as the +name+ argument, so + # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>. + # + # [collection(force_reload = false)] + # Returns an array of all the associated objects. + # 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 + # parent object, unless the parent object is a new record. + # This will also run validations and callbacks of associated object(s). + # [collection.delete(object, ...)] + # Removes one or more objects from the collection by setting their foreign keys to +NULL+. + # Objects will be in addition destroyed if they're associated with <tt>dependent: :destroy</tt>, + # and deleted if they're associated with <tt>dependent: :delete_all</tt>. + # + # If the <tt>:through</tt> option is used, then the join records are deleted (rather than + # nullified) by default, but you can specify <tt>dependent: :destroy</tt> or + # <tt>dependent: :nullify</tt> to override this. + # [collection.destroy(object, ...)] + # Removes one or more objects from the collection by running <tt>destroy</tt> on + # each record, regardless of any dependent option, ensuring callbacks are run. + # + # If the <tt>:through</tt> option is used, then the join records are destroyed + # instead, not the objects themselves. + # [collection=objects] + # Replaces the collections content by deleting and adding objects as appropriate. If the <tt>:through</tt> + # option is true callbacks in the join models are triggered except destroy callbacks, since deletion is + # direct by default. You can specify <tt>dependent: :destroy</tt> or + # <tt>dependent: :nullify</tt> to override this. + # [collection_singular_ids] + # Returns an array of the associated objects' ids + # [collection_singular_ids=ids] + # Replace the collection with the objects identified by the primary keys in +ids+. This + # method loads the models and calls <tt>collection=</tt>. See above. + # [collection.clear] + # Removes every object from the collection. This destroys the associated objects if they + # are associated with <tt>dependent: :destroy</tt>, deletes them directly from the + # database if <tt>dependent: :delete_all</tt>, otherwise sets their foreign keys to +NULL+. + # If the <tt>:through</tt> option is true no destroy callbacks are invoked on the join models. + # Join models are directly deleted. + # [collection.empty?] + # Returns +true+ if there are no associated objects. + # [collection.size] + # Returns the number of associated objects. + # [collection.find(...)] + # Finds an associated object according to the same rules as ActiveRecord::FinderMethods#find. + # [collection.exists?(...)] + # Checks whether an associated object with the given conditions exists. + # Uses the same rules as ActiveRecord::FinderMethods#exists?. + # [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 + # been saved. + # [collection.create(attributes = {})] + # Returns a new object of the collection type that has been instantiated + # with +attributes+, linked to this object through a foreign key, and that has already + # been saved (if it passed the validation). *Note*: This only works if the base model + # already exists in the DB, not if it is a new (unsaved) record! + # [collection.create!(attributes = {})] + # Does the same as <tt>collection.create</tt>, but raises ActiveRecord::RecordInvalid + # if the record is invalid. + # + # === Example + # + # 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> + # * <tt>Firm#clients.destroy</tt> + # * <tt>Firm#clients=</tt> + # * <tt>Firm#client_ids</tt> + # * <tt>Firm#client_ids=</tt> + # * <tt>Firm#clients.clear</tt> + # * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>) + # * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>) + # * <tt>Firm#clients.find</tt> (similar to <tt>Client.where(firm_id: id).find(id)</tt>) + # * <tt>Firm#clients.exists?(name: 'ACME')</tt> (similar to <tt>Client.exists?(name: 'ACME', firm_id: firm.id)</tt>) + # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>) + # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>) + # * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save!</tt>) + # The declaration can also include an +options+ hash to specialize the behavior of the association. + # + # === Scopes + # + # You can pass a second argument +scope+ as a callable (i.e. proc or + # lambda) to retrieve a specific set of records or customize the generated + # query when you access the associated collection. + # + # Scope examples: + # has_many :comments, -> { where(author_id: 1) } + # has_many :employees, -> { joins(:address) } + # has_many :posts, ->(post) { where("max_post_length > ?", post.length) } + # + # === Extensions + # + # The +extension+ argument allows you to pass a block into a has_many + # association. This is useful for adding new finders, creators and other + # factory-type methods to be used as part of the association. + # + # Extension examples: + # has_many :employees do + # def find_or_create_by_name(name) + # first_name, last_name = name.split(" ", 2) + # find_or_create_by(first_name: first_name, last_name: last_name) + # end + # end + # + # === Options + # [:class_name] + # Specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So <tt>has_many :products</tt> will by default be linked + # to the +Product+ class, but if the real class name is +SpecialProduct+, you'll have to + # specify it with this option. + # [:foreign_key] + # Specify the foreign key used for the association. By default this is guessed to be the name + # of this class in lower-case and "_id" suffixed. So a Person class that makes a #has_many + # association will use "person_id" as the default <tt>:foreign_key</tt>. + # [:foreign_type] + # Specify the column used to store the associated object's type, if this is a polymorphic + # association. By default this is guessed to be the name of the polymorphic association + # specified on "as" option with a "_type" suffix. So a class that defines a + # <tt>has_many :tags, as: :taggable</tt> association will use "taggable_type" as the + # default <tt>:foreign_type</tt>. + # [:primary_key] + # Specify the name of the column to use as the primary key for the association. By default this is +id+. + # [:dependent] + # 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 <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). + # * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Callbacks are not executed. + # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records. + # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects. + # + # If using with the <tt>:through</tt> option, the association on the join model must be + # a #belongs_to, and the records which get deleted are the join records, rather than + # the associated records. + # [:counter_cache] + # This option can be used to configure a custom named <tt>:counter_cache.</tt> You only need this option, + # when you customized the name of your <tt>:counter_cache</tt> on the #belongs_to association. + # [:as] + # Specifies a polymorphic interface (See #belongs_to). + # [:through] + # Specifies an association through which to perform the query. This can be any other type + # of association, including other <tt>:through</tt> associations. Options for <tt>:class_name</tt>, + # <tt>:primary_key</tt> and <tt>:foreign_key</tt> are ignored, as the association uses the + # source reflection. + # + # If the association on the join model is a #belongs_to, the collection can be modified + # and the records on the <tt>:through</tt> model will be automatically created and removed + # as appropriate. Otherwise, the collection is read-only, so you should manipulate the + # <tt>:through</tt> association directly. + # + # If you are going to modify the association (rather than just read from it), then it is + # a good idea to set the <tt>:inverse_of</tt> option on the source association on the + # join model. This allows associated records to be built which will automatically create + # the appropriate join model records when they are saved. (See the 'Association Join Models' + # section above.) + # [:source] + # Specifies the source association name used by #has_many <tt>:through</tt> queries. + # Only use it if the name cannot be inferred from the association. + # <tt>has_many :subscribers, through: :subscriptions</tt> will look for either <tt>:subscribers</tt> or + # <tt>:subscriber</tt> on Subscription, unless a <tt>:source</tt> is given. + # [:source_type] + # Specifies type of the source association used by #has_many <tt>:through</tt> queries where the source + # association is a polymorphic #belongs_to. + # [:validate] + # When set to +true+, validates new objects added to association when saving the parent object. +true+ by default. + # If you want to ensure associated objects are revalidated on every update, use +validates_associated+. + # [:autosave] + # 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. + # + # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets + # <tt>:autosave</tt> to <tt>true</tt>. + # [:inverse_of] + # Specifies the name of the #belongs_to association on the associated object + # that is the inverse of this #has_many association. Does not work in combination + # with <tt>:through</tt> or <tt>:as</tt> options. + # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. + # [:extend] + # Specifies a module or array of modules that will be extended into the association object returned. + # Useful for defining methods on associations, especially when they should be shared between multiple + # association objects. + # + # Option examples: + # has_many :comments, -> { order("posted_on") } + # has_many :comments, -> { includes(:author) } + # has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person" + # has_many :tracks, -> { order("position") }, dependent: :destroy + # has_many :comments, dependent: :nullify + # has_many :tags, as: :taggable + # has_many :reports, -> { readonly } + # has_many :subscribers, through: :subscriptions, source: :user + def has_many(name, scope = nil, options = {}, &extension) + reflection = Builder::HasMany.build(self, name, scope, options, &extension) + Reflection.add_reflection self, name, reflection + end - # Specifies a one-to-one association with another class. This method should only be used - # if this class contains the foreign key. If the other class contains the foreign key, - # then you should use #has_one instead. See also ActiveRecord::Associations::ClassMethods's overview - # on when to use #has_one and when to use #belongs_to. - # - # Methods will be added for retrieval and query for a single associated object, for which - # this object holds an id: - # - # +association+ is a placeholder for the symbol passed as the +name+ argument, so - # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>. - # - # [association(force_reload = false)] - # Returns the associated object. +nil+ is returned if none is found. - # [association=(associate)] - # Assigns the associate object, extracts the primary key, and sets it as the foreign key. - # [build_association(attributes = {})] - # Returns a new object of the associated type that has been instantiated - # with +attributes+ and linked to this object through a foreign key, but has not yet been saved. - # [create_association(attributes = {})] - # Returns a new object of the associated type that has been instantiated - # with +attributes+, linked to this object through a foreign key, and that - # has already been saved (if it passed the validation). - # [create_association!(attributes = {})] - # Does the same as <tt>create_association</tt>, but raises ActiveRecord::RecordInvalid - # if the record is invalid. - # - # === Example - # - # A Post class declares <tt>belongs_to :author</tt>, which will add: - # * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>) - # * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>) - # * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>) - # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>) - # * <tt>Post#create_author!</tt> (similar to <tt>post.author = Author.new; post.author.save!; post.author</tt>) - # The declaration can also include an +options+ hash to specialize the behavior of the association. - # - # === Scopes - # - # You can pass a second argument +scope+ as a callable (i.e. proc or - # lambda) to retrieve a specific record or customize the generated query - # when you access the associated object. - # - # Scope examples: - # belongs_to :firm, -> { where(id: 2) } - # belongs_to :user, -> { joins(:friends) } - # belongs_to :level, ->(level) { where("game_level > ?", level.current) } - # - # === Options - # - # [:class_name] - # Specify the class name of the association. Use it only if that name can't be inferred - # from the association name. So <tt>belongs_to :author</tt> will by default be linked to the Author class, but - # if the real class name is Person, you'll have to specify it with this option. - # [:foreign_key] - # Specify the foreign key used for the association. By default this is guessed to be the name - # of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt> - # association will use "person_id" as the default <tt>:foreign_key</tt>. Similarly, - # <tt>belongs_to :favorite_person, class_name: "Person"</tt> will use a foreign key - # of "favorite_person_id". - # [:foreign_type] - # Specify the column used to store the associated object's type, if this is a polymorphic - # association. By default this is guessed to be the name of the association with a "_type" - # suffix. So a class that defines a <tt>belongs_to :taggable, polymorphic: true</tt> - # association will use "taggable_type" as the default <tt>:foreign_type</tt>. - # [:primary_key] - # Specify the method that returns the primary key of associated object used for the association. - # By default this is id. - # [:dependent] - # If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to - # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. - # This option should not be specified when #belongs_to is used in conjunction with - # a #has_many relationship on another class because of the potential to leave - # orphaned records behind. - # [:counter_cache] - # Caches the number of belonging objects on the associate class through the use of CounterCache::ClassMethods#increment_counter - # and CounterCache::ClassMethods#decrement_counter. The counter cache is incremented when an object of this - # 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 <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>.) - # Note: Specifying a counter cache will add it to that model's list of readonly attributes - # using +attr_readonly+. - # [:polymorphic] - # Specify this association is a polymorphic association by passing +true+. - # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute - # to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>). - # [:validate] - # When set to +true+, validates new objects added to association when saving the parent object. +false+ by default. - # If you want to ensure associated objects are revalidated on every update, use +validates_associated+. - # [:autosave] - # If true, always save the associated object or destroy it if marked for destruction, when - # saving the parent object. - # If false, never save or destroy the associated object. - # By default, only save the associated object if it's a new record. - # - # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for - # sets <tt>:autosave</tt> to <tt>true</tt>. - # [:touch] - # If true, the associated object will be touched (the updated_at/on attributes set to current time) - # when this record is either saved or destroyed. If you specify a symbol, that attribute - # will be updated with the current time in addition to the updated_at/on attribute. - # Please note that with touching no validation is performed and only the +after_touch+, - # +after_commit+ and +after_rollback+ callbacks are executed. - # [:inverse_of] - # Specifies the name of the #has_one or #has_many association on the associated - # object that is the inverse of this #belongs_to association. Does not work in - # combination with the <tt>:polymorphic</tt> options. - # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. - # [:optional] - # When set to +true+, the association will not have its presence validated. - # [:required] - # When set to +true+, the association will also have its presence validated. - # This will validate the association itself, not the id. You can use - # +:inverse_of+ to avoid an extra query during validation. - # NOTE: <tt>required</tt> is set to <tt>true</tt> by default and is deprecated. If - # you don't want to have association presence validated, use <tt>optional: true</tt>. - # - # Option examples: - # belongs_to :firm, foreign_key: "client_of" - # belongs_to :person, primary_key: "name", foreign_key: "person_name" - # belongs_to :author, class_name: "Person", foreign_key: "author_id" - # belongs_to :valid_coupon, ->(o) { where "discounts > ?", o.payments_count }, - # class_name: "Coupon", foreign_key: "coupon_id" - # belongs_to :attachable, polymorphic: true - # belongs_to :project, -> { readonly } - # belongs_to :post, counter_cache: true - # belongs_to :comment, touch: true - # belongs_to :company, touch: :employees_last_updated_at - # belongs_to :user, optional: true - def belongs_to(name, scope = nil, options = {}) - reflection = Builder::BelongsTo.build(self, name, scope, options) - Reflection.add_reflection self, name, reflection - end + # 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. + # + # The following methods for retrieval and query of a single associated object will be added: + # + # +association+ is a placeholder for the symbol passed as the +name+ argument, so + # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>. + # + # [association(force_reload = false)] + # Returns the associated object. +nil+ is returned if none is found. + # [association=(associate)] + # Assigns the associate object, extracts the primary key, sets it as the foreign key, + # and saves the associate object. To avoid database inconsistencies, permanently deletes an existing + # associated object when assigning a new one, even if the new one isn't saved to database. + # [build_association(attributes = {})] + # Returns a new object of the associated type that has been instantiated + # with +attributes+ and linked to this object through a foreign key, but has not + # yet been saved. + # [create_association(attributes = {})] + # Returns a new object of the associated type that has been instantiated + # with +attributes+, linked to this object through a foreign key, and that + # has already been saved (if it passed the validation). + # [create_association!(attributes = {})] + # Does the same as <tt>create_association</tt>, but raises ActiveRecord::RecordInvalid + # if the record is invalid. + # + # === Example + # + # An Account class declares <tt>has_one :beneficiary</tt>, which will add: + # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.where(account_id: id).first</tt>) + # * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>) + # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>) + # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>) + # * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save!; b</tt>) + # + # === Scopes + # + # You can pass a second argument +scope+ as a callable (i.e. proc or + # lambda) to retrieve a specific record or customize the generated query + # when you access the associated object. + # + # Scope examples: + # has_one :author, -> { where(comment_id: 1) } + # has_one :employer, -> { joins(:company) } + # has_one :dob, ->(dob) { where("Date.new(2000, 01, 01) > ?", dob) } + # + # === Options + # + # The declaration can also include an +options+ hash to specialize the behavior of the association. + # + # Options are: + # [:class_name] + # Specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So <tt>has_one :manager</tt> will by default be linked to the Manager class, but + # if the real class name is Person, you'll have to specify it with this option. + # [:dependent] + # Controls what happens to the associated object when + # its owner is destroyed: + # + # * <tt>:destroy</tt> causes the associated object to also be destroyed + # * <tt>:delete</tt> causes the associated object to be deleted directly from the database (so callbacks will not execute) + # * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Callbacks are not executed. + # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there is an associated record + # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there is an associated object + # + # Note that <tt>:dependent</tt> option is ignored when using <tt>:through</tt> option. + # [:foreign_key] + # Specify the foreign key used for the association. By default this is guessed to be the name + # of this class in lower-case and "_id" suffixed. So a Person class that makes a #has_one association + # will use "person_id" as the default <tt>:foreign_key</tt>. + # [:foreign_type] + # Specify the column used to store the associated object's type, if this is a polymorphic + # association. By default this is guessed to be the name of the polymorphic association + # specified on "as" option with a "_type" suffix. So a class that defines a + # <tt>has_one :tag, as: :taggable</tt> association will use "taggable_type" as the + # default <tt>:foreign_type</tt>. + # [:primary_key] + # Specify the method that returns the primary key used for the association. By default this is +id+. + # [:as] + # Specifies a polymorphic interface (See #belongs_to). + # [:through] + # Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt>, + # <tt>:primary_key</tt>, and <tt>:foreign_key</tt> are ignored, as the association uses the + # source reflection. You can only use a <tt>:through</tt> query through a #has_one + # or #belongs_to association on the join model. + # [:source] + # Specifies the source association name used by #has_one <tt>:through</tt> queries. + # Only use it if the name cannot be inferred from the association. + # <tt>has_one :favorite, through: :favorites</tt> will look for a + # <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given. + # [:source_type] + # Specifies type of the source association used by #has_one <tt>:through</tt> queries where the source + # association is a polymorphic #belongs_to. + # [:validate] + # When set to +true+, validates new objects added to association when saving the parent object. +false+ by default. + # If you want to ensure associated objects are revalidated on every update, use +validates_associated+. + # [:autosave] + # If true, always save the associated object or destroy it if marked for destruction, + # when saving the parent object. If false, never save or destroy the associated object. + # By default, only save the associated object if it's a new record. + # + # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets + # <tt>:autosave</tt> to <tt>true</tt>. + # [:inverse_of] + # Specifies the name of the #belongs_to association on the associated object + # that is the inverse of this #has_one association. Does not work in combination + # with <tt>:through</tt> or <tt>:as</tt> options. + # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. + # [:required] + # When set to +true+, the association will also have its presence validated. + # This will validate the association itself, not the id. You can use + # +:inverse_of+ to avoid an extra query during validation. + # + # Option examples: + # has_one :credit_card, dependent: :destroy # destroys the associated credit card + # has_one :credit_card, dependent: :nullify # updates the associated records foreign + # # key value to NULL rather than destroying it + # has_one :last_comment, -> { order('posted_on') }, class_name: "Comment" + # has_one :project_manager, -> { where(role: 'project_manager') }, class_name: "Person" + # has_one :attachment, as: :attachable + # has_one :boss, -> { readonly } + # has_one :club, through: :membership + # has_one :primary_address, -> { where(primary: true) }, through: :addressables, source: :addressable + # has_one :credit_card, required: true + def has_one(name, scope = nil, options = {}) + reflection = Builder::HasOne.build(self, name, scope, options) + Reflection.add_reflection self, name, reflection + end - # Specifies a many-to-many relationship with another class. This associates two classes via an - # intermediate join table. Unless the join table is explicitly specified as an option, it is - # guessed using the lexical order of the class names. So a join between Developer and Project - # will give the default join table name of "developers_projects" because "D" precedes "P" alphabetically. - # Note that this precedence is calculated using the <tt><</tt> operator for String. This - # means that if the strings are of different lengths, and the strings are equal when compared - # up to the shortest length, then the longer string is considered of higher - # lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" - # to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", - # but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the - # custom <tt>:join_table</tt> option if you need to. - # If your tables share a common prefix, it will only appear once at the beginning. For example, - # the tables "catalog_categories" and "catalog_products" generate a join table name of "catalog_categories_products". - # - # The join table should not have a primary key or a model associated with it. You must manually generate the - # join table with a migration such as this: - # - # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[5.0] - # def change - # create_join_table :developers, :projects - # end - # end - # - # It's also a good idea to add indexes to each of those columns to speed up the joins process. - # However, in MySQL it is advised to add a compound index for both of the columns as MySQL only - # uses one index per table during the lookup. - # - # Adds the following methods for retrieval and query: - # - # +collection+ is a placeholder for the symbol passed as the +name+ argument, so - # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>. - # - # [collection(force_reload = false)] - # Returns an array of all the associated objects. - # An empty array is returned if none are found. - # [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 - # 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. - # This does not destroy the objects. - # [collection.destroy(object, ...)] - # Removes one or more objects from the collection by running destroy on each association in the join table, overriding any dependent option. - # This does not destroy the objects. - # [collection=objects] - # Replaces the collection's content by deleting and adding objects as appropriate. - # [collection_singular_ids] - # Returns an array of the associated objects' ids. - # [collection_singular_ids=ids] - # Replace the collection by the objects identified by the primary keys in +ids+. - # [collection.clear] - # Removes every object from the collection. This does not destroy the objects. - # [collection.empty?] - # Returns +true+ if there are no associated objects. - # [collection.size] - # Returns the number of associated objects. - # [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::FinderMethods#find. - # [collection.exists?(...)] - # Checks whether an associated object with the given conditions exists. - # Uses the same rules as ActiveRecord::FinderMethods#exists?. - # [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. - # [collection.create(attributes = {})] - # Returns a new object of the collection type that has been instantiated - # with +attributes+, linked to this object through the join table, and that has already been - # saved (if it passed the validation). - # - # === Example - # - # A Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add: - # * <tt>Developer#projects</tt> - # * <tt>Developer#projects<<</tt> - # * <tt>Developer#projects.delete</tt> - # * <tt>Developer#projects.destroy</tt> - # * <tt>Developer#projects=</tt> - # * <tt>Developer#project_ids</tt> - # * <tt>Developer#project_ids=</tt> - # * <tt>Developer#projects.clear</tt> - # * <tt>Developer#projects.empty?</tt> - # * <tt>Developer#projects.size</tt> - # * <tt>Developer#projects.find(id)</tt> - # * <tt>Developer#projects.exists?(...)</tt> - # * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("developer_id" => id)</tt>) - # * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("developer_id" => id); c.save; c</tt>) - # The declaration may include an +options+ hash to specialize the behavior of the association. - # - # === Scopes - # - # You can pass a second argument +scope+ as a callable (i.e. proc or - # lambda) to retrieve a specific set of records or customize the generated - # query when you access the associated collection. - # - # Scope examples: - # has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) } - # has_and_belongs_to_many :categories, ->(category) { - # where("default_category = ?", category.name) - # } - # - # === Extensions - # - # The +extension+ argument allows you to pass a block into a - # has_and_belongs_to_many association. This is useful for adding new - # finders, creators and other factory-type methods to be used as part of - # the association. - # - # Extension examples: - # has_and_belongs_to_many :contractors do - # def find_or_create_by_name(name) - # first_name, last_name = name.split(" ", 2) - # find_or_create_by(first_name: first_name, last_name: last_name) - # end - # end - # - # === Options - # - # [:class_name] - # Specify the class name of the association. Use it only if that name can't be inferred - # from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the - # Project class, but if the real class name is SuperProject, you'll have to specify it with this option. - # [:join_table] - # Specify the name of the join table if the default based on lexical order isn't what you want. - # <b>WARNING:</b> If you're overwriting the table name of either class, the +table_name+ method - # MUST be declared underneath any #has_and_belongs_to_many declaration in order to work. - # [:foreign_key] - # Specify the foreign key used for the association. By default this is guessed to be the name - # of this class in lower-case and "_id" suffixed. So a Person class that makes - # a #has_and_belongs_to_many association to Project will use "person_id" as the - # default <tt>:foreign_key</tt>. - # [:association_foreign_key] - # Specify the foreign key used for the association on the receiving side of the association. - # By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed. - # So if a Person class makes a #has_and_belongs_to_many association to Project, - # the association will use "project_id" as the default <tt>:association_foreign_key</tt>. - # [:validate] - # When set to +true+, validates new objects added to association when saving the parent object. +true+ by default. - # If you want to ensure associated objects are revalidated on every update, use +validates_associated+. - # [:autosave] - # 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. - # - # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets - # <tt>:autosave</tt> to <tt>true</tt>. - # - # Option examples: - # has_and_belongs_to_many :projects - # has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) } - # has_and_belongs_to_many :nations, class_name: "Country" - # has_and_belongs_to_many :categories, join_table: "prods_cats" - # has_and_belongs_to_many :categories, -> { readonly } - def has_and_belongs_to_many(name, scope = nil, options = {}, &extension) - if scope.is_a?(Hash) - options = scope - scope = nil + # Specifies a one-to-one association with another class. This method should only be used + # if this class contains the foreign key. If the other class contains the foreign key, + # then you should use #has_one instead. See also ActiveRecord::Associations::ClassMethods's overview + # on when to use #has_one and when to use #belongs_to. + # + # Methods will be added for retrieval and query for a single associated object, for which + # this object holds an id: + # + # +association+ is a placeholder for the symbol passed as the +name+ argument, so + # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>. + # + # [association(force_reload = false)] + # Returns the associated object. +nil+ is returned if none is found. + # [association=(associate)] + # Assigns the associate object, extracts the primary key, and sets it as the foreign key. + # [build_association(attributes = {})] + # Returns a new object of the associated type that has been instantiated + # with +attributes+ and linked to this object through a foreign key, but has not yet been saved. + # [create_association(attributes = {})] + # Returns a new object of the associated type that has been instantiated + # with +attributes+, linked to this object through a foreign key, and that + # has already been saved (if it passed the validation). + # [create_association!(attributes = {})] + # Does the same as <tt>create_association</tt>, but raises ActiveRecord::RecordInvalid + # if the record is invalid. + # + # === Example + # + # A Post class declares <tt>belongs_to :author</tt>, which will add: + # * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>) + # * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>) + # * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>) + # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>) + # * <tt>Post#create_author!</tt> (similar to <tt>post.author = Author.new; post.author.save!; post.author</tt>) + # The declaration can also include an +options+ hash to specialize the behavior of the association. + # + # === Scopes + # + # You can pass a second argument +scope+ as a callable (i.e. proc or + # lambda) to retrieve a specific record or customize the generated query + # when you access the associated object. + # + # Scope examples: + # belongs_to :firm, -> { where(id: 2) } + # belongs_to :user, -> { joins(:friends) } + # belongs_to :level, ->(level) { where("game_level > ?", level.current) } + # + # === Options + # + # [:class_name] + # Specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So <tt>belongs_to :author</tt> will by default be linked to the Author class, but + # if the real class name is Person, you'll have to specify it with this option. + # [:foreign_key] + # Specify the foreign key used for the association. By default this is guessed to be the name + # of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt> + # association will use "person_id" as the default <tt>:foreign_key</tt>. Similarly, + # <tt>belongs_to :favorite_person, class_name: "Person"</tt> will use a foreign key + # of "favorite_person_id". + # [:foreign_type] + # Specify the column used to store the associated object's type, if this is a polymorphic + # association. By default this is guessed to be the name of the association with a "_type" + # suffix. So a class that defines a <tt>belongs_to :taggable, polymorphic: true</tt> + # association will use "taggable_type" as the default <tt>:foreign_type</tt>. + # [:primary_key] + # Specify the method that returns the primary key of associated object used for the association. + # By default this is id. + # [:dependent] + # If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to + # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. + # This option should not be specified when #belongs_to is used in conjunction with + # a #has_many relationship on another class because of the potential to leave + # orphaned records behind. + # [:counter_cache] + # Caches the number of belonging objects on the associate class through the use of CounterCache::ClassMethods#increment_counter + # and CounterCache::ClassMethods#decrement_counter. The counter cache is incremented when an object of this + # 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 <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>.) + # Note: Specifying a counter cache will add it to that model's list of readonly attributes + # using +attr_readonly+. + # [:polymorphic] + # Specify this association is a polymorphic association by passing +true+. + # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute + # to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>). + # [:validate] + # When set to +true+, validates new objects added to association when saving the parent object. +false+ by default. + # If you want to ensure associated objects are revalidated on every update, use +validates_associated+. + # [:autosave] + # If true, always save the associated object or destroy it if marked for destruction, when + # saving the parent object. + # If false, never save or destroy the associated object. + # By default, only save the associated object if it's a new record. + # + # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for + # sets <tt>:autosave</tt> to <tt>true</tt>. + # [:touch] + # If true, the associated object will be touched (the updated_at/on attributes set to current time) + # when this record is either saved or destroyed. If you specify a symbol, that attribute + # will be updated with the current time in addition to the updated_at/on attribute. + # Please note that with touching no validation is performed and only the +after_touch+, + # +after_commit+ and +after_rollback+ callbacks are executed. + # [:inverse_of] + # Specifies the name of the #has_one or #has_many association on the associated + # object that is the inverse of this #belongs_to association. Does not work in + # combination with the <tt>:polymorphic</tt> options. + # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. + # [:optional] + # When set to +true+, the association will not have its presence validated. + # [:required] + # When set to +true+, the association will also have its presence validated. + # This will validate the association itself, not the id. You can use + # +:inverse_of+ to avoid an extra query during validation. + # NOTE: <tt>required</tt> is set to <tt>true</tt> by default and is deprecated. If + # you don't want to have association presence validated, use <tt>optional: true</tt>. + # + # Option examples: + # belongs_to :firm, foreign_key: "client_of" + # belongs_to :person, primary_key: "name", foreign_key: "person_name" + # belongs_to :author, class_name: "Person", foreign_key: "author_id" + # belongs_to :valid_coupon, ->(o) { where "discounts > ?", o.payments_count }, + # class_name: "Coupon", foreign_key: "coupon_id" + # belongs_to :attachable, polymorphic: true + # belongs_to :project, -> { readonly } + # belongs_to :post, counter_cache: true + # belongs_to :comment, touch: true + # belongs_to :company, touch: :employees_last_updated_at + # belongs_to :user, optional: true + def belongs_to(name, scope = nil, options = {}) + reflection = Builder::BelongsTo.build(self, name, scope, options) + Reflection.add_reflection self, name, reflection end - habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(name, scope, options, self) + # Specifies a many-to-many relationship with another class. This associates two classes via an + # intermediate join table. Unless the join table is explicitly specified as an option, it is + # guessed using the lexical order of the class names. So a join between Developer and Project + # will give the default join table name of "developers_projects" because "D" precedes "P" alphabetically. + # Note that this precedence is calculated using the <tt><</tt> operator for String. This + # means that if the strings are of different lengths, and the strings are equal when compared + # up to the shortest length, then the longer string is considered of higher + # lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" + # to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", + # but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the + # custom <tt>:join_table</tt> option if you need to. + # If your tables share a common prefix, it will only appear once at the beginning. For example, + # the tables "catalog_categories" and "catalog_products" generate a join table name of "catalog_categories_products". + # + # The join table should not have a primary key or a model associated with it. You must manually generate the + # join table with a migration such as this: + # + # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[5.0] + # def change + # create_join_table :developers, :projects + # end + # end + # + # It's also a good idea to add indexes to each of those columns to speed up the joins process. + # However, in MySQL it is advised to add a compound index for both of the columns as MySQL only + # uses one index per table during the lookup. + # + # Adds the following methods for retrieval and query: + # + # +collection+ is a placeholder for the symbol passed as the +name+ argument, so + # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>. + # + # [collection(force_reload = false)] + # Returns an array of all the associated objects. + # An empty array is returned if none are found. + # [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 + # 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. + # This does not destroy the objects. + # [collection.destroy(object, ...)] + # Removes one or more objects from the collection by running destroy on each association in the join table, overriding any dependent option. + # This does not destroy the objects. + # [collection=objects] + # Replaces the collection's content by deleting and adding objects as appropriate. + # [collection_singular_ids] + # Returns an array of the associated objects' ids. + # [collection_singular_ids=ids] + # Replace the collection by the objects identified by the primary keys in +ids+. + # [collection.clear] + # Removes every object from the collection. This does not destroy the objects. + # [collection.empty?] + # Returns +true+ if there are no associated objects. + # [collection.size] + # Returns the number of associated objects. + # [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::FinderMethods#find. + # [collection.exists?(...)] + # Checks whether an associated object with the given conditions exists. + # Uses the same rules as ActiveRecord::FinderMethods#exists?. + # [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. + # [collection.create(attributes = {})] + # Returns a new object of the collection type that has been instantiated + # with +attributes+, linked to this object through the join table, and that has already been + # saved (if it passed the validation). + # + # === Example + # + # A Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add: + # * <tt>Developer#projects</tt> + # * <tt>Developer#projects<<</tt> + # * <tt>Developer#projects.delete</tt> + # * <tt>Developer#projects.destroy</tt> + # * <tt>Developer#projects=</tt> + # * <tt>Developer#project_ids</tt> + # * <tt>Developer#project_ids=</tt> + # * <tt>Developer#projects.clear</tt> + # * <tt>Developer#projects.empty?</tt> + # * <tt>Developer#projects.size</tt> + # * <tt>Developer#projects.find(id)</tt> + # * <tt>Developer#projects.exists?(...)</tt> + # * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("developer_id" => id)</tt>) + # * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("developer_id" => id); c.save; c</tt>) + # The declaration may include an +options+ hash to specialize the behavior of the association. + # + # === Scopes + # + # You can pass a second argument +scope+ as a callable (i.e. proc or + # lambda) to retrieve a specific set of records or customize the generated + # query when you access the associated collection. + # + # Scope examples: + # has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) } + # has_and_belongs_to_many :categories, ->(category) { + # where("default_category = ?", category.name) + # } + # + # === Extensions + # + # The +extension+ argument allows you to pass a block into a + # has_and_belongs_to_many association. This is useful for adding new + # finders, creators and other factory-type methods to be used as part of + # the association. + # + # Extension examples: + # has_and_belongs_to_many :contractors do + # def find_or_create_by_name(name) + # first_name, last_name = name.split(" ", 2) + # find_or_create_by(first_name: first_name, last_name: last_name) + # end + # end + # + # === Options + # + # [:class_name] + # Specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the + # Project class, but if the real class name is SuperProject, you'll have to specify it with this option. + # [:join_table] + # Specify the name of the join table if the default based on lexical order isn't what you want. + # <b>WARNING:</b> If you're overwriting the table name of either class, the +table_name+ method + # MUST be declared underneath any #has_and_belongs_to_many declaration in order to work. + # [:foreign_key] + # Specify the foreign key used for the association. By default this is guessed to be the name + # of this class in lower-case and "_id" suffixed. So a Person class that makes + # a #has_and_belongs_to_many association to Project will use "person_id" as the + # default <tt>:foreign_key</tt>. + # [:association_foreign_key] + # Specify the foreign key used for the association on the receiving side of the association. + # By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed. + # So if a Person class makes a #has_and_belongs_to_many association to Project, + # the association will use "project_id" as the default <tt>:association_foreign_key</tt>. + # [:validate] + # When set to +true+, validates new objects added to association when saving the parent object. +true+ by default. + # If you want to ensure associated objects are revalidated on every update, use +validates_associated+. + # [:autosave] + # 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. + # + # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets + # <tt>:autosave</tt> to <tt>true</tt>. + # + # Option examples: + # has_and_belongs_to_many :projects + # has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) } + # has_and_belongs_to_many :nations, class_name: "Country" + # has_and_belongs_to_many :categories, join_table: "prods_cats" + # has_and_belongs_to_many :categories, -> { readonly } + def has_and_belongs_to_many(name, scope = nil, options = {}, &extension) + if scope.is_a?(Hash) + options = scope + scope = nil + end + + habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(name, scope, options, self) - builder = Builder::HasAndBelongsToMany.new name, self, options + builder = Builder::HasAndBelongsToMany.new name, self, options - join_model = builder.through_model + join_model = builder.through_model - const_set join_model.name, join_model - private_constant join_model.name + const_set join_model.name, join_model + private_constant join_model.name - middle_reflection = builder.middle_reflection join_model + middle_reflection = builder.middle_reflection join_model - Builder::HasMany.define_callbacks self, middle_reflection - Reflection.add_reflection self, middle_reflection.name, middle_reflection - middle_reflection.parent_reflection = habtm_reflection + Builder::HasMany.define_callbacks self, middle_reflection + Reflection.add_reflection self, middle_reflection.name, middle_reflection + middle_reflection.parent_reflection = habtm_reflection - include Module.new { - class_eval <<-RUBY, __FILE__, __LINE__ + 1 + include Module.new { + class_eval <<-RUBY, __FILE__, __LINE__ + 1 def destroy_associations association(:#{middle_reflection.name}).delete_all(:delete_all) association(:#{name}).reset super end RUBY - } + } - hm_options = {} - hm_options[:through] = middle_reflection.name - hm_options[:source] = join_model.right_reflection.name + hm_options = {} + hm_options[:through] = middle_reflection.name + hm_options[:source] = join_model.right_reflection.name - [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend].each do |k| - hm_options[k] = options[k] if options.key? k - end + [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend].each do |k| + hm_options[k] = options[k] if options.key? k + end - has_many name, scope, hm_options, &extension - self._reflections[name.to_s].parent_reflection = habtm_reflection + has_many name, scope, hm_options, &extension + self._reflections[name.to_s].parent_reflection = habtm_reflection + end end - end end end diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 15844de0bc..12f8c1ccd4 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -51,115 +51,115 @@ module ActiveRecord protected - attr_reader :value_transformation + attr_reader :value_transformation private - def join(table, constraint) - table.create_join(table, table.create_on(constraint), join_type) - end + def join(table, constraint) + table.create_join(table, table.create_on(constraint), join_type) + end + + def last_chain_scope(scope, table, reflection, owner, association_klass) + join_keys = reflection.join_keys(association_klass) + key = join_keys.key + foreign_key = join_keys.foreign_key - def last_chain_scope(scope, table, reflection, owner, association_klass) - join_keys = reflection.join_keys(association_klass) - key = join_keys.key - foreign_key = join_keys.foreign_key + value = transform_value(owner[foreign_key]) + scope = scope.where(table.name => { key => value }) - value = transform_value(owner[foreign_key]) - scope = scope.where(table.name => { key => value }) + if reflection.type + polymorphic_type = transform_value(owner.class.base_class.name) + scope = scope.where(table.name => { reflection.type => polymorphic_type }) + end - if reflection.type - polymorphic_type = transform_value(owner.class.base_class.name) - scope = scope.where(table.name => { reflection.type => polymorphic_type }) + scope end - scope - end + def transform_value(value) + value_transformation.call(value) + end - def transform_value(value) - value_transformation.call(value) - end + def next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection) + join_keys = reflection.join_keys(association_klass) + key = join_keys.key + foreign_key = join_keys.foreign_key - def next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection) - join_keys = reflection.join_keys(association_klass) - key = join_keys.key - foreign_key = join_keys.foreign_key + constraint = table[key].eq(foreign_table[foreign_key]) - constraint = table[key].eq(foreign_table[foreign_key]) + if reflection.type + value = transform_value(next_reflection.klass.base_class.name) + scope = scope.where(table.name => { reflection.type => value }) + end - if reflection.type - value = transform_value(next_reflection.klass.base_class.name) - scope = scope.where(table.name => { reflection.type => value }) + scope = scope.joins(join(foreign_table, constraint)) end - scope = scope.joins(join(foreign_table, constraint)) - end + class ReflectionProxy < SimpleDelegator # :nodoc: + attr_accessor :next + attr_reader :alias_name - class ReflectionProxy < SimpleDelegator # :nodoc: - attr_accessor :next - attr_reader :alias_name + def initialize(reflection, alias_name) + super(reflection) + @alias_name = alias_name + end - def initialize(reflection, alias_name) - super(reflection) - @alias_name = alias_name + def all_includes; nil; end end - def all_includes; nil; end - end - - def get_chain(reflection, association, tracker) - name = reflection.name - runtime_reflection = Reflection::RuntimeReflection.new(reflection, association) - previous_reflection = runtime_reflection - reflection.chain.drop(1).each do |refl| - alias_name = tracker.aliased_table_for(refl.table_name, refl.alias_candidate(name)) - proxy = ReflectionProxy.new(refl, alias_name) - previous_reflection.next = proxy - previous_reflection = proxy + def get_chain(reflection, association, tracker) + name = reflection.name + runtime_reflection = Reflection::RuntimeReflection.new(reflection, association) + previous_reflection = runtime_reflection + reflection.chain.drop(1).each do |refl| + alias_name = tracker.aliased_table_for(refl.table_name, refl.alias_candidate(name)) + proxy = ReflectionProxy.new(refl, alias_name) + previous_reflection.next = proxy + previous_reflection = proxy + end + [runtime_reflection, previous_reflection] end - [runtime_reflection, previous_reflection] - end - def add_constraints(scope, owner, association_klass, refl, chain_head, chain_tail) - owner_reflection = chain_tail - table = owner_reflection.alias_name - scope = last_chain_scope(scope, table, owner_reflection, owner, association_klass) + def add_constraints(scope, owner, association_klass, refl, chain_head, chain_tail) + owner_reflection = chain_tail + table = owner_reflection.alias_name + scope = last_chain_scope(scope, table, owner_reflection, owner, association_klass) - reflection = chain_head - while reflection - table = reflection.alias_name + reflection = chain_head + while reflection + table = reflection.alias_name - unless reflection == chain_tail - next_reflection = reflection.next - foreign_table = next_reflection.alias_name - scope = next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection) - end + unless reflection == chain_tail + next_reflection = reflection.next + foreign_table = next_reflection.alias_name + scope = next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection) + end - # Exclude the scope of the association itself, because that - # was already merged in the #scope method. - reflection.constraints.each do |scope_chain_item| - item = eval_scope(reflection.klass, scope_chain_item, owner) + # Exclude the scope of the association itself, because that + # was already merged in the #scope method. + reflection.constraints.each do |scope_chain_item| + item = eval_scope(reflection.klass, scope_chain_item, owner) - if scope_chain_item == refl.scope - scope.merge! item.except(:where, :includes) - end + if scope_chain_item == refl.scope + scope.merge! item.except(:where, :includes) + end + + reflection.all_includes do + scope.includes! item.includes_values + end - reflection.all_includes do - scope.includes! item.includes_values + scope.unscope!(*item.unscope_values) + scope.where_clause += item.where_clause + scope.order_values |= item.order_values end - scope.unscope!(*item.unscope_values) - scope.where_clause += item.where_clause - scope.order_values |= item.order_values + reflection = reflection.next end - reflection = reflection.next + scope end - scope - end - - def eval_scope(klass, scope, owner) - klass.unscoped.instance_exec(owner, &scope) - end + def eval_scope(klass, scope, owner) + klass.unscoped.instance_exec(owner, &scope) + end end end end diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb index 33c8d4b8d6..047292b2bd 100644 --- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb @@ -16,9 +16,9 @@ module ActiveRecord::Associations::Builder # :nodoc: private - def klass - @lhs_class.send(:compute_type, @rhs_class_name) - end + def klass + @lhs_class.send(:compute_type, @rhs_class_name) + end end def self.build(lhs_class, name, options) @@ -105,29 +105,29 @@ module ActiveRecord::Associations::Builder # :nodoc: private - def middle_options(join_model) - middle_options = {} - middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}" - middle_options[:source] = join_model.left_reflection.name - if options.key? :foreign_key - middle_options[:foreign_key] = options[:foreign_key] + def middle_options(join_model) + middle_options = {} + middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}" + middle_options[:source] = join_model.left_reflection.name + if options.key? :foreign_key + middle_options[:foreign_key] = options[:foreign_key] + end + middle_options end - middle_options - end - def belongs_to_options(options) - rhs_options = {} + def belongs_to_options(options) + rhs_options = {} - if options.key? :class_name - rhs_options[:foreign_key] = options[:class_name].to_s.foreign_key - rhs_options[:class_name] = options[:class_name] - end + if options.key? :class_name + rhs_options[:foreign_key] = options[:class_name].to_s.foreign_key + rhs_options[:class_name] = options[:class_name] + end - if options.key? :association_foreign_key - rhs_options[:foreign_key] = options[:association_foreign_key] - end + if options.key? :association_foreign_key + rhs_options[:foreign_key] = options[:association_foreign_key] + end - rhs_options - end + rhs_options + end end end diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index c8805d107b..7d106015d7 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -221,7 +221,7 @@ module ActiveRecord end dependent = if dependent - dependent + dependent elsif options[:dependent] == :destroy :delete_all else diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index 0f9ad77405..3946d5baa4 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -167,134 +167,134 @@ module ActiveRecord private - def make_constraints(parent, child, tables, join_type) - chain = child.reflection.chain - foreign_table = parent.table - foreign_klass = parent.base_klass - child.join_constraints(foreign_table, foreign_klass, child, join_type, tables, child.reflection.scope_chain, chain) - end - - def make_outer_joins(parent, child) - tables = table_aliases_for(parent, child) - join_type = Arel::Nodes::OuterJoin - info = make_constraints parent, child, tables, join_type - - [info] + child.children.flat_map { |c| make_outer_joins(child, c) } - end + def make_constraints(parent, child, tables, join_type) + chain = child.reflection.chain + foreign_table = parent.table + foreign_klass = parent.base_klass + child.join_constraints(foreign_table, foreign_klass, child, join_type, tables, child.reflection.scope_chain, chain) + end - def make_left_outer_joins(parent, child) - tables = child.tables - join_type = Arel::Nodes::OuterJoin - info = make_constraints parent, child, tables, join_type + def make_outer_joins(parent, child) + tables = table_aliases_for(parent, child) + join_type = Arel::Nodes::OuterJoin + info = make_constraints parent, child, tables, join_type - [info] + child.children.flat_map { |c| make_left_outer_joins(child, c) } - end + [info] + child.children.flat_map { |c| make_outer_joins(child, c) } + end - def make_inner_joins(parent, child) - tables = child.tables - join_type = Arel::Nodes::InnerJoin - info = make_constraints parent, child, tables, join_type + def make_left_outer_joins(parent, child) + tables = child.tables + join_type = Arel::Nodes::OuterJoin + info = make_constraints parent, child, tables, join_type - [info] + child.children.flat_map { |c| make_inner_joins(child, c) } - end + [info] + child.children.flat_map { |c| make_left_outer_joins(child, c) } + end - def table_aliases_for(parent, node) - node.reflection.chain.map { |reflection| - alias_tracker.aliased_table_for( - reflection.table_name, - table_alias_for(reflection, parent, reflection != node.reflection) - ) - } - end + def make_inner_joins(parent, child) + tables = child.tables + join_type = Arel::Nodes::InnerJoin + info = make_constraints parent, child, tables, join_type - def construct_tables!(parent, node) - node.tables = table_aliases_for(parent, node) - node.children.each { |child| construct_tables! node, child } - end + [info] + child.children.flat_map { |c| make_inner_joins(child, c) } + end - def table_alias_for(reflection, parent, join) - name = "#{reflection.plural_name}_#{parent.table_name}" - name << "_join" if join - name - end + def table_aliases_for(parent, node) + node.reflection.chain.map { |reflection| + alias_tracker.aliased_table_for( + reflection.table_name, + table_alias_for(reflection, parent, reflection != node.reflection) + ) + } + end - def walk(left, right) - intersection, missing = right.children.map { |node1| - [left.children.find { |node2| node1.match? node2 }, node1] - }.partition(&:first) + def construct_tables!(parent, node) + node.tables = table_aliases_for(parent, node) + node.children.each { |child| construct_tables! node, child } + end - ojs = missing.flat_map { |_,n| make_outer_joins left, n } - intersection.flat_map { |l,r| walk l, r }.concat ojs - end + def table_alias_for(reflection, parent, join) + name = "#{reflection.plural_name}_#{parent.table_name}" + name << "_join" if join + name + end - def find_reflection(klass, name) - klass._reflect_on_association(name) or - raise ConfigurationError, "Can't join '#{ klass.name }' to association named '#{ name }'; perhaps you misspelled it?" - end + def walk(left, right) + intersection, missing = right.children.map { |node1| + [left.children.find { |node2| node1.match? node2 }, node1] + }.partition(&:first) - def build(associations, base_klass) - associations.map do |name, right| - reflection = find_reflection base_klass, name - reflection.check_validity! - reflection.check_eager_loadable! + ojs = missing.flat_map { |_,n| make_outer_joins left, n } + intersection.flat_map { |l,r| walk l, r }.concat ojs + end - if reflection.polymorphic? - next unless @eager_loading - raise EagerLoadPolymorphicError.new(reflection) - end + def find_reflection(klass, name) + klass._reflect_on_association(name) or + raise ConfigurationError, "Can't join '#{ klass.name }' to association named '#{ name }'; perhaps you misspelled it?" + end - JoinAssociation.new reflection, build(right, reflection.klass) - end.compact - end + def build(associations, base_klass) + associations.map do |name, right| + reflection = find_reflection base_klass, name + reflection.check_validity! + reflection.check_eager_loadable! - def construct(ar_parent, parent, row, rs, seen, model_cache, aliases) - return if ar_parent.nil? + if reflection.polymorphic? + next unless @eager_loading + raise EagerLoadPolymorphicError.new(reflection) + end - parent.children.each do |node| - if node.reflection.collection? - other = ar_parent.association(node.reflection.name) - other.loaded! - elsif ar_parent.association_cached?(node.reflection.name) - model = ar_parent.association(node.reflection.name).target - construct(model, node, row, rs, seen, model_cache, aliases) - next - end + JoinAssociation.new reflection, build(right, reflection.klass) + end.compact + end - key = aliases.column_alias(node, node.primary_key) - id = row[key] - if id.nil? - nil_association = ar_parent.association(node.reflection.name) - nil_association.loaded! - next + def construct(ar_parent, parent, row, rs, seen, model_cache, aliases) + return if ar_parent.nil? + + parent.children.each do |node| + if node.reflection.collection? + other = ar_parent.association(node.reflection.name) + other.loaded! + elsif ar_parent.association_cached?(node.reflection.name) + model = ar_parent.association(node.reflection.name).target + construct(model, node, row, rs, seen, model_cache, aliases) + next + end + + key = aliases.column_alias(node, node.primary_key) + id = row[key] + if id.nil? + nil_association = ar_parent.association(node.reflection.name) + nil_association.loaded! + next + end + + model = seen[ar_parent.object_id][node.base_klass][id] + + if model + construct(model, node, row, rs, seen, model_cache, aliases) + else + model = construct_model(ar_parent, node, row, model_cache, id, aliases) + model.readonly! + seen[ar_parent.object_id][node.base_klass][id] = model + construct(model, node, row, rs, seen, model_cache, aliases) + end end + end - model = seen[ar_parent.object_id][node.base_klass][id] + def construct_model(record, node, row, model_cache, id, aliases) + model = model_cache[node][id] ||= node.instantiate(row, + aliases.column_aliases(node)) + other = record.association(node.reflection.name) - if model - construct(model, node, row, rs, seen, model_cache, aliases) + if node.reflection.collection? + other.target.push(model) else - model = construct_model(ar_parent, node, row, model_cache, id, aliases) - model.readonly! - seen[ar_parent.object_id][node.base_klass][id] = model - construct(model, node, row, rs, seen, model_cache, aliases) + other.target = model end - end - end - def construct_model(record, node, row, model_cache, id, aliases) - model = model_cache[node][id] ||= node.instantiate(row, - aliases.column_aliases(node)) - other = record.association(node.reflection.name) - - if node.reflection.collection? - other.target.push(model) - else - other.target = model + other.set_inverse_instance(model) + model end - - other.set_inverse_instance(model) - model - end end end end diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index 320d9dc952..a81860e40f 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -107,30 +107,30 @@ module ActiveRecord private # Loads all the given data into +records+ for the +association+. - def preloaders_on(association, records, scope) - case association - when Hash - preloaders_for_hash(association, records, scope) - when Symbol - preloaders_for_one(association, records, scope) - when String - preloaders_for_one(association.to_sym, records, scope) - else - raise ArgumentError, "#{association.inspect} was not recognized for preload" + def preloaders_on(association, records, scope) + case association + when Hash + preloaders_for_hash(association, records, scope) + when Symbol + preloaders_for_one(association, records, scope) + when String + preloaders_for_one(association.to_sym, records, scope) + else + raise ArgumentError, "#{association.inspect} was not recognized for preload" + end end - end - def preloaders_for_hash(association, records, scope) - association.flat_map { |parent, child| - loaders = preloaders_for_one parent, records, scope + def preloaders_for_hash(association, records, scope) + association.flat_map { |parent, child| + loaders = preloaders_for_one parent, records, scope - recs = loaders.flat_map(&:preloaded_records).uniq - loaders.concat Array.wrap(child).flat_map { |assoc| - preloaders_on assoc, recs, scope + recs = loaders.flat_map(&:preloaded_records).uniq + loaders.concat Array.wrap(child).flat_map { |assoc| + preloaders_on assoc, recs, scope + } + loaders } - loaders - } - end + end # Loads all the given data into +records+ for a singular +association+. # @@ -144,70 +144,70 @@ module ActiveRecord # Additionally, polymorphic belongs_to associations can have multiple associated # classes, depending on the polymorphic_type field. So we group by the classes as # well. - def preloaders_for_one(association, records, scope) - grouped_records(association, records).flat_map do |reflection, klasses| - klasses.map do |rhs_klass, rs| - loader = preloader_for(reflection, rs, rhs_klass).new(rhs_klass, rs, reflection, scope) - loader.run self - loader + def preloaders_for_one(association, records, scope) + grouped_records(association, records).flat_map do |reflection, klasses| + klasses.map do |rhs_klass, rs| + loader = preloader_for(reflection, rs, rhs_klass).new(rhs_klass, rs, reflection, scope) + loader.run self + loader + end end end - end - def grouped_records(association, records) - h = {} - records.each do |record| - next unless record - assoc = record.association(association) - klasses = h[assoc.reflection] ||= {} - (klasses[assoc.klass] ||= []) << record + def grouped_records(association, records) + h = {} + records.each do |record| + next unless record + assoc = record.association(association) + klasses = h[assoc.reflection] ||= {} + (klasses[assoc.klass] ||= []) << record + end + h end - h - end - class AlreadyLoaded # :nodoc: - attr_reader :owners, :reflection + class AlreadyLoaded # :nodoc: + attr_reader :owners, :reflection - def initialize(klass, owners, reflection, preload_scope) - @owners = owners - @reflection = reflection - end + def initialize(klass, owners, reflection, preload_scope) + @owners = owners + @reflection = reflection + end - def run(preloader); end + def run(preloader); end - def preloaded_records - owners.flat_map { |owner| owner.association(reflection.name).target } + def preloaded_records + owners.flat_map { |owner| owner.association(reflection.name).target } + end end - end - class NullPreloader # :nodoc: - def self.new(klass, owners, reflection, preload_scope); self; end - def self.run(preloader); end - def self.preloaded_records; []; end - def self.owners; []; end - end + class NullPreloader # :nodoc: + def self.new(klass, owners, reflection, preload_scope); self; end + def self.run(preloader); end + def self.preloaded_records; []; end + def self.owners; []; end + end # Returns a class containing the logic needed to load preload the data # and attach it to a relation. For example +Preloader::Association+ or # +Preloader::HasManyThrough+. The class returned implements a `run` method # that accepts a preloader. - def preloader_for(reflection, owners, rhs_klass) - return NullPreloader unless rhs_klass + def preloader_for(reflection, owners, rhs_klass) + return NullPreloader unless rhs_klass - if owners.first.association(reflection.name).loaded? - return AlreadyLoaded - end - reflection.check_preloadable! - - case reflection.macro - when :has_many - reflection.options[:through] ? HasManyThrough : HasMany - when :has_one - reflection.options[:through] ? HasOneThrough : HasOne - when :belongs_to - BelongsTo + if owners.first.association(reflection.name).loaded? + return AlreadyLoaded + end + reflection.check_preloadable! + + case reflection.macro + when :has_many + reflection.options[:through] ? HasManyThrough : HasMany + when :has_one + reflection.options[:through] ? HasOneThrough : HasOne + when :belongs_to + BelongsTo + end end - end end end end diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 3032bc786e..a8afa48865 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -61,99 +61,99 @@ module ActiveRecord private - def associated_records_by_owner(preloader) - records = load_records - owners.each_with_object({}) do |owner, result| - result[owner] = records[convert_key(owner[owner_key_name])] || [] + def associated_records_by_owner(preloader) + records = load_records + owners.each_with_object({}) do |owner, result| + result[owner] = records[convert_key(owner[owner_key_name])] || [] + end end - end - def owner_keys - unless defined?(@owner_keys) - @owner_keys = owners.map do |owner| - owner[owner_key_name] + def owner_keys + unless defined?(@owner_keys) + @owner_keys = owners.map do |owner| + owner[owner_key_name] + end + @owner_keys.uniq! + @owner_keys.compact! end - @owner_keys.uniq! - @owner_keys.compact! + @owner_keys end - @owner_keys - end - def key_conversion_required? - @key_conversion_required ||= association_key_type != owner_key_type - end + def key_conversion_required? + @key_conversion_required ||= association_key_type != owner_key_type + end - def convert_key(key) - if key_conversion_required? - key.to_s - else - key + def convert_key(key) + if key_conversion_required? + key.to_s + else + key + end end - end - def association_key_type - @klass.type_for_attribute(association_key_name.to_s).type - end + def association_key_type + @klass.type_for_attribute(association_key_name.to_s).type + end - def owner_key_type - @model.type_for_attribute(owner_key_name.to_s).type - end + def owner_key_type + @model.type_for_attribute(owner_key_name.to_s).type + end - def load_records - return {} if owner_keys.empty? - # 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 - slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size) - @preloaded_records = slices.flat_map do |slice| - records_for(slice) + def load_records + return {} if owner_keys.empty? + # 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 + slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size) + @preloaded_records = slices.flat_map do |slice| + records_for(slice) + end + @preloaded_records.group_by do |record| + convert_key(record[association_key_name]) + end end - @preloaded_records.group_by do |record| - convert_key(record[association_key_name]) + + def reflection_scope + @reflection_scope ||= reflection.scope ? klass.unscoped.instance_exec(nil, &reflection.scope) : klass.unscoped end - end - def reflection_scope - @reflection_scope ||= reflection.scope ? klass.unscoped.instance_exec(nil, &reflection.scope) : klass.unscoped - end + def build_scope + scope = klass.unscoped - def build_scope - scope = klass.unscoped + values = reflection_scope.values + preload_values = preload_scope.values - values = reflection_scope.values - preload_values = preload_scope.values + scope.where_clause = reflection_scope.where_clause + preload_scope.where_clause + scope.references_values = Array(values[:references]) + Array(preload_values[:references]) - scope.where_clause = reflection_scope.where_clause + preload_scope.where_clause - scope.references_values = Array(values[:references]) + Array(preload_values[:references]) + if preload_values[:select] || values[:select] + scope._select!(preload_values[:select] || values[:select]) + end + scope.includes! preload_values[:includes] || values[:includes] + if preload_scope.joins_values.any? + scope.joins!(preload_scope.joins_values) + else + scope.joins!(reflection_scope.joins_values) + end - if preload_values[:select] || values[:select] - scope._select!(preload_values[:select] || values[:select]) - end - scope.includes! preload_values[:includes] || values[:includes] - if preload_scope.joins_values.any? - scope.joins!(preload_scope.joins_values) - else - scope.joins!(reflection_scope.joins_values) - end + if order_values = preload_values[:order] || values[:order] + scope.order!(order_values) + end - if order_values = preload_values[:order] || values[:order] - scope.order!(order_values) - end + if preload_values[:reordering] || values[:reordering] + scope.reordering_value = true + end - if preload_values[:reordering] || values[:reordering] - scope.reordering_value = true - end + if preload_values[:readonly] || values[:readonly] + scope.readonly! + end - if preload_values[:readonly] || values[:readonly] - scope.readonly! - end + if options[:as] + scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name }) + end - if options[:as] - scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name }) + scope.unscope_values = Array(values[:unscope]) + Array(preload_values[:unscope]) + klass.default_scoped.merge(scope) end - - scope.unscope_values = Array(values[:unscope]) + Array(preload_values[:unscope]) - klass.default_scoped.merge(scope) - end end end end diff --git a/activerecord/lib/active_record/associations/preloader/collection_association.rb b/activerecord/lib/active_record/associations/preloader/collection_association.rb index 9939280fa4..24b8e01029 100644 --- a/activerecord/lib/active_record/associations/preloader/collection_association.rb +++ b/activerecord/lib/active_record/associations/preloader/collection_association.rb @@ -4,14 +4,14 @@ module ActiveRecord class CollectionAssociation < Association #:nodoc: private - def preload(preloader) - associated_records_by_owner(preloader).each do |owner, records| - association = owner.association(reflection.name) - association.loaded! - association.target.concat(records) - records.each { |record| association.set_inverse_instance(record) } + def preload(preloader) + associated_records_by_owner(preloader).each do |owner, records| + association = owner.association(reflection.name) + association.loaded! + association.target.concat(records) + records.each { |record| association.set_inverse_instance(record) } + end end - end end end end diff --git a/activerecord/lib/active_record/associations/preloader/singular_association.rb b/activerecord/lib/active_record/associations/preloader/singular_association.rb index f60647a81e..9f8d98c5a5 100644 --- a/activerecord/lib/active_record/associations/preloader/singular_association.rb +++ b/activerecord/lib/active_record/associations/preloader/singular_association.rb @@ -5,15 +5,15 @@ module ActiveRecord private - def preload(preloader) - associated_records_by_owner(preloader).each do |owner, associated_records| - record = associated_records.first + def preload(preloader) + associated_records_by_owner(preloader).each do |owner, associated_records| + record = associated_records.first - association = owner.association(reflection.name) - association.target = record - association.set_inverse_instance(record) if record + association = owner.association(reflection.name) + association.target = record + association.set_inverse_instance(record) if record + end end - end end end diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index b0203909ce..f53c30f80e 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -61,48 +61,48 @@ module ActiveRecord private - def id_to_index_map(ids) - id_map = {} - ids.each_with_index { |id, index| id_map[id] = index } - id_map - end + def id_to_index_map(ids) + id_map = {} + ids.each_with_index { |id, index| id_map[id] = index } + id_map + end - def reset_association(owners, association_name) - should_reset = (through_scope != through_reflection.klass.unscoped) || - (reflection.options[:source_type] && through_reflection.collection?) + def reset_association(owners, association_name) + should_reset = (through_scope != through_reflection.klass.unscoped) || + (reflection.options[:source_type] && through_reflection.collection?) - # Don't cache the association - we would only be caching a subset - if should_reset - owners.each { |owner| - owner.association(association_name).reset - } + # Don't cache the association - we would only be caching a subset + if should_reset + owners.each { |owner| + owner.association(association_name).reset + } + end end - end - def through_scope - scope = through_reflection.klass.unscoped + def through_scope + scope = through_reflection.klass.unscoped - if options[:source_type] - scope.where! reflection.foreign_type => options[:source_type] - else - unless reflection_scope.where_clause.empty? - scope.includes_values = Array(reflection_scope.values[:includes] || options[:source]) - scope.where_clause = reflection_scope.where_clause - end + if options[:source_type] + scope.where! reflection.foreign_type => options[:source_type] + else + unless reflection_scope.where_clause.empty? + scope.includes_values = Array(reflection_scope.values[:includes] || options[:source]) + scope.where_clause = reflection_scope.where_clause + end - scope.references! reflection_scope.values[:references] - if scope.eager_loading? && order_values = reflection_scope.values[:order] - scope = scope.order(order_values) + scope.references! reflection_scope.values[:references] + if scope.eager_loading? && order_values = reflection_scope.values[:order] + scope = scope.order(order_values) + end end - end - scope - end + scope + end - def target_records_from_association(association) - association.loaded? ? association.target : association.reader - end + def target_records_from_association(association) + association.loaded? ? association.target : association.reader + end end end end diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb index 2f41e9e45b..95b3adae5f 100644 --- a/activerecord/lib/active_record/attribute.rb +++ b/activerecord/lib/active_record/attribute.rb @@ -130,108 +130,108 @@ module ActiveRecord protected - attr_reader :original_attribute - alias_method :assigned?, :original_attribute + attr_reader :original_attribute + alias_method :assigned?, :original_attribute - def initialize_dup(other) - if defined?(@value) && @value.duplicable? - @value = @value.dup + def initialize_dup(other) + if defined?(@value) && @value.duplicable? + @value = @value.dup + end end - end - - def changed_from_assignment? - assigned? && type.changed?(original_value, value, value_before_type_cast) - end - def original_value_for_database - if assigned? - original_attribute.original_value_for_database - else - _original_value_for_database + def changed_from_assignment? + assigned? && type.changed?(original_value, value, value_before_type_cast) end - end - - def _original_value_for_database - type.serialize(original_value) - end - class FromDatabase < Attribute # :nodoc: - def type_cast(value) - type.deserialize(value) + def original_value_for_database + if assigned? + original_attribute.original_value_for_database + else + _original_value_for_database + end end def _original_value_for_database - value_before_type_cast + type.serialize(original_value) end - end - class FromUser < Attribute # :nodoc: - def type_cast(value) - type.cast(value) - end + class FromDatabase < Attribute # :nodoc: + def type_cast(value) + type.deserialize(value) + end - def came_from_user? - true + def _original_value_for_database + value_before_type_cast + end end - end - class WithCastValue < Attribute # :nodoc: - def type_cast(value) - value - end + class FromUser < Attribute # :nodoc: + def type_cast(value) + type.cast(value) + end - def changed_in_place? - false + def came_from_user? + true + end end - end - class Null < Attribute # :nodoc: - def initialize(name) - super(name, nil, Type::Value.new) - end + class WithCastValue < Attribute # :nodoc: + def type_cast(value) + value + end - def type_cast(*) - nil + def changed_in_place? + false + end end - def with_type(type) - self.class.with_cast_value(name, nil, type) - end + class Null < Attribute # :nodoc: + def initialize(name) + super(name, nil, Type::Value.new) + end - def with_value_from_database(value) - raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`" - end - alias_method :with_value_from_user, :with_value_from_database - end + def type_cast(*) + nil + end - class Uninitialized < Attribute # :nodoc: - UNINITIALIZED_ORIGINAL_VALUE = Object.new + def with_type(type) + self.class.with_cast_value(name, nil, type) + end - def initialize(name, type) - super(name, nil, type) + def with_value_from_database(value) + raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`" + end + alias_method :with_value_from_user, :with_value_from_database end - def value - if block_given? - yield name + class Uninitialized < Attribute # :nodoc: + UNINITIALIZED_ORIGINAL_VALUE = Object.new + + def initialize(name, type) + super(name, nil, type) end - end - def original_value - UNINITIALIZED_ORIGINAL_VALUE - end + def value + if block_given? + yield name + end + end - def value_for_database - end + def original_value + UNINITIALIZED_ORIGINAL_VALUE + end - def initialized? - false - end + def value_for_database + end + + def initialized? + false + end - def with_type(type) - self.class.new(name, type) + def with_type(type) + self.class.new(name, type) + end end - end - private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue + private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue end end diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activerecord/lib/active_record/attribute/user_provided_default.rb index 32b9c289bb..a4e2c2ec85 100644 --- a/activerecord/lib/active_record/attribute/user_provided_default.rb +++ b/activerecord/lib/active_record/attribute/user_provided_default.rb @@ -22,7 +22,7 @@ module ActiveRecord protected - attr_reader :user_provided_value + attr_reader :user_provided_value end end end diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index 1cccb0852e..f3ce52fdfe 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -12,27 +12,27 @@ module ActiveRecord private - def _assign_attributes(attributes) # :nodoc: - multi_parameter_attributes = {} - nested_parameter_attributes = {} + def _assign_attributes(attributes) # :nodoc: + multi_parameter_attributes = {} + nested_parameter_attributes = {} - attributes.each do |k, v| - if k.include?("(") - multi_parameter_attributes[k] = attributes.delete(k) - elsif v.is_a?(Hash) - nested_parameter_attributes[k] = attributes.delete(k) + attributes.each do |k, v| + if k.include?("(") + multi_parameter_attributes[k] = attributes.delete(k) + elsif v.is_a?(Hash) + nested_parameter_attributes[k] = attributes.delete(k) + end end - end - super(attributes) + super(attributes) - assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty? - assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty? - end + assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty? + assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty? + end # Assign any deferred nested attributes after the base attributes have been set. - def assign_nested_parameter_attributes(pairs) - pairs.each { |k, v| _assign_attribute(k, v) } - end + def assign_nested_parameter_attributes(pairs) + pairs.each { |k, v| _assign_attribute(k, v) } + end # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done # by calling new on the column type or aggregation type (through composed_of) object with these parameters. @@ -40,52 +40,52 @@ module ActiveRecord # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+. - def assign_multiparameter_attributes(pairs) - execute_callstack_for_multiparameter_attributes( - extract_callstack_for_multiparameter_attributes(pairs) - ) - end + def assign_multiparameter_attributes(pairs) + execute_callstack_for_multiparameter_attributes( + extract_callstack_for_multiparameter_attributes(pairs) + ) + end - def execute_callstack_for_multiparameter_attributes(callstack) - errors = [] - callstack.each do |name, values_with_empty_parameters| - begin - if values_with_empty_parameters.each_value.all?(&:nil?) - values = nil - else - values = values_with_empty_parameters + def execute_callstack_for_multiparameter_attributes(callstack) + errors = [] + callstack.each do |name, values_with_empty_parameters| + begin + if values_with_empty_parameters.each_value.all?(&:nil?) + values = nil + else + values = values_with_empty_parameters + end + send("#{name}=", values) + rescue => ex + errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name) end - send("#{name}=", values) - rescue => ex - errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name) + end + unless errors.empty? + error_descriptions = errors.map(&:message).join(",") + raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]" end end - unless errors.empty? - error_descriptions = errors.map(&:message).join(",") - raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]" - end - end - def extract_callstack_for_multiparameter_attributes(pairs) - attributes = {} + def extract_callstack_for_multiparameter_attributes(pairs) + attributes = {} - pairs.each do |(multiparameter_name, value)| - attribute_name = multiparameter_name.split("(").first - attributes[attribute_name] ||= {} + pairs.each do |(multiparameter_name, value)| + attribute_name = multiparameter_name.split("(").first + attributes[attribute_name] ||= {} - parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value) - attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value - end + parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value) + attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value + end - attributes - end + attributes + end - def type_cast_attribute_value(multiparameter_name, value) - multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value - end + def type_cast_attribute_value(multiparameter_name, value) + multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value + end - def find_parameter_position(multiparameter_name) - multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i - end + def find_parameter_position(multiparameter_name) + multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i + end end end diff --git a/activerecord/lib/active_record/attribute_decorators.rb b/activerecord/lib/active_record/attribute_decorators.rb index 7d0ae32411..340dfe11cf 100644 --- a/activerecord/lib/active_record/attribute_decorators.rb +++ b/activerecord/lib/active_record/attribute_decorators.rb @@ -24,13 +24,13 @@ module ActiveRecord private - def load_schema! - super - attribute_types.each do |name, type| - decorated_type = attribute_type_decorations.apply(name, type) - define_attribute(name, decorated_type) + def load_schema! + super + attribute_types.each do |name, type| + decorated_type = attribute_type_decorations.apply(name, type) + define_attribute(name, decorated_type) + end end - end end class TypeDecorator # :nodoc: @@ -53,15 +53,15 @@ module ActiveRecord private - def decorators_for(name, type) - matching(name, type).map(&:last) - end + def decorators_for(name, type) + matching(name, type).map(&:last) + end - def matching(name, type) - @decorations.values.select do |(matcher, _)| - matcher.call(name, type) + def matching(name, type) + @decorations.values.select do |(matcher, _)| + matcher.call(name, type) + end end - end end end end diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 12699bb30b..367e7fba84 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -161,7 +161,7 @@ module ActiveRecord # # => ["id", "created_at", "updated_at", "name", "age"] def attribute_names @attribute_names ||= if !abstract_class? && table_exists? - attribute_types.keys + attribute_types.keys else [] end @@ -394,65 +394,65 @@ module ActiveRecord protected - def clone_attribute_value(reader_method, attribute_name) # :nodoc: - value = send(reader_method, attribute_name) - value.duplicable? ? value.clone : value - rescue TypeError, NoMethodError - value - end + def clone_attribute_value(reader_method, attribute_name) # :nodoc: + value = send(reader_method, attribute_name) + value.duplicable? ? value.clone : value + rescue TypeError, NoMethodError + value + end - def arel_attributes_with_values_for_create(attribute_names) # :nodoc: - arel_attributes_with_values(attributes_for_create(attribute_names)) - end + def arel_attributes_with_values_for_create(attribute_names) # :nodoc: + arel_attributes_with_values(attributes_for_create(attribute_names)) + end - def arel_attributes_with_values_for_update(attribute_names) # :nodoc: - arel_attributes_with_values(attributes_for_update(attribute_names)) - end + def arel_attributes_with_values_for_update(attribute_names) # :nodoc: + arel_attributes_with_values(attributes_for_update(attribute_names)) + end - def attribute_method?(attr_name) # :nodoc: - # We check defined? because Syck calls respond_to? before actually calling initialize. - defined?(@attributes) && @attributes.key?(attr_name) - end + def attribute_method?(attr_name) # :nodoc: + # We check defined? because Syck calls respond_to? before actually calling initialize. + defined?(@attributes) && @attributes.key?(attr_name) + end private # Returns a Hash of the Arel::Attributes and attribute values that have been # typecasted for use in an Arel insert/update method. - def arel_attributes_with_values(attribute_names) - attrs = {} - arel_table = self.class.arel_table + def arel_attributes_with_values(attribute_names) + attrs = {} + arel_table = self.class.arel_table - attribute_names.each do |name| - attrs[arel_table[name]] = typecasted_attribute_value(name) + attribute_names.each do |name| + attrs[arel_table[name]] = typecasted_attribute_value(name) + end + attrs end - attrs - end # Filters the primary keys and readonly attributes from the attribute names. - def attributes_for_update(attribute_names) - attribute_names.reject do |name| - readonly_attribute?(name) + def attributes_for_update(attribute_names) + attribute_names.reject do |name| + readonly_attribute?(name) + end end - end # Filters out the primary keys, from the attribute names, when the primary # key is to be generated (e.g. the id attribute has no value). - def attributes_for_create(attribute_names) - attribute_names.reject do |name| - pk_attribute?(name) && id.nil? + def attributes_for_create(attribute_names) + attribute_names.reject do |name| + pk_attribute?(name) && id.nil? + end end - end - def readonly_attribute?(name) - self.class.readonly_attributes.include?(name) - end + def readonly_attribute?(name) + self.class.readonly_attributes.include?(name) + end - def pk_attribute?(name) - name == self.class.primary_key - end + def pk_attribute?(name) + name == self.class.primary_key + end - def typecasted_attribute_value(name) - _read_attribute(name) - end + def typecasted_attribute_value(name) + _read_attribute(name) + end 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 1db6776688..92f124078c 100644 --- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb +++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb @@ -64,13 +64,13 @@ module ActiveRecord private # Handle *_before_type_cast for method_missing. - def attribute_before_type_cast(attribute_name) - read_attribute_before_type_cast(attribute_name) - end + def attribute_before_type_cast(attribute_name) + read_attribute_before_type_cast(attribute_name) + end - def attribute_came_from_user?(attribute_name) - @attributes[attribute_name].came_from_user? - end + def attribute_came_from_user?(attribute_name) + @attributes[attribute_name].came_from_user? + end end end end diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 50d7e1df7a..c9638bf70b 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -100,52 +100,52 @@ module ActiveRecord private - def mutation_tracker - unless defined?(@mutation_tracker) - @mutation_tracker = nil + def mutation_tracker + unless defined?(@mutation_tracker) + @mutation_tracker = nil + end + @mutation_tracker ||= AttributeMutationTracker.new(@attributes) end - @mutation_tracker ||= AttributeMutationTracker.new(@attributes) - end - def changes_include?(attr_name) - super || mutation_tracker.changed?(attr_name) - end + def changes_include?(attr_name) + super || mutation_tracker.changed?(attr_name) + end - def clear_attribute_change(attr_name) - mutation_tracker.forget_change(attr_name) - end + def clear_attribute_change(attr_name) + mutation_tracker.forget_change(attr_name) + end - def _update_record(*) - partial_writes? ? super(keys_for_partial_write) : super - end + def _update_record(*) + partial_writes? ? super(keys_for_partial_write) : super + end - def _create_record(*) - partial_writes? ? super(keys_for_partial_write) : super - end + def _create_record(*) + partial_writes? ? super(keys_for_partial_write) : super + end - def keys_for_partial_write - changed & self.class.column_names - end + def keys_for_partial_write + changed & self.class.column_names + end - def store_original_attributes - @attributes = @attributes.map(&:forgetting_assignment) - @mutation_tracker = nil - end + def store_original_attributes + @attributes = @attributes.map(&:forgetting_assignment) + @mutation_tracker = nil + end - def previous_mutation_tracker - @previous_mutation_tracker ||= NullMutationTracker.instance - end + def previous_mutation_tracker + @previous_mutation_tracker ||= NullMutationTracker.instance + end - def cache_changed_attributes - @cached_changed_attributes = changed_attributes - yield - ensure - clear_changed_attributes_cache - end + def cache_changed_attributes + @cached_changed_attributes = changed_attributes + yield + ensure + clear_changed_attributes_cache + end - def clear_changed_attributes_cache - remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes) - end + def clear_changed_attributes_cache + remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes) + end end end end diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 162a564a49..9e99ed8ac1 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -47,95 +47,95 @@ module ActiveRecord protected - def attribute_method?(attr_name) - attr_name == "id" || super - end + def attribute_method?(attr_name) + attr_name == "id" || super + end - module ClassMethods - def define_method_attribute(attr_name) - super + module ClassMethods + def define_method_attribute(attr_name) + super - if attr_name == primary_key && attr_name != "id" - generated_attribute_methods.send(:alias_method, :id, primary_key) + if attr_name == primary_key && attr_name != "id" + generated_attribute_methods.send(:alias_method, :id, primary_key) + end end - end - ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was).to_set + ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was).to_set - def dangerous_attribute_method?(method_name) - super && !ID_ATTRIBUTE_METHODS.include?(method_name) - end + def dangerous_attribute_method?(method_name) + super && !ID_ATTRIBUTE_METHODS.include?(method_name) + end - # Defines the primary key field -- can be overridden in subclasses. - # Overwriting will negate any effect of the +primary_key_prefix_type+ - # setting, though. - def primary_key - @primary_key = reset_primary_key unless defined? @primary_key - @primary_key - end + # Defines the primary key field -- can be overridden in subclasses. + # Overwriting will negate any effect of the +primary_key_prefix_type+ + # setting, though. + def primary_key + @primary_key = reset_primary_key unless defined? @primary_key + @primary_key + end - # Returns a quoted version of the primary key name, used to construct - # SQL statements. - def quoted_primary_key - @quoted_primary_key ||= connection.quote_column_name(primary_key) - end + # Returns a quoted version of the primary key name, used to construct + # SQL statements. + def quoted_primary_key + @quoted_primary_key ||= connection.quote_column_name(primary_key) + end - def reset_primary_key #:nodoc: - if self == base_class - self.primary_key = get_primary_key(base_class.name) - else - self.primary_key = base_class.primary_key + def reset_primary_key #:nodoc: + if self == base_class + self.primary_key = get_primary_key(base_class.name) + else + self.primary_key = base_class.primary_key + end end - end - def get_primary_key(base_name) #:nodoc: - if base_name && primary_key_prefix_type == :table_name - base_name.foreign_key(false) - elsif base_name && primary_key_prefix_type == :table_name_with_underscore - base_name.foreign_key - else - if ActiveRecord::Base != self && table_exists? - pk = connection.schema_cache.primary_keys(table_name) - suppress_composite_primary_key(pk) + def get_primary_key(base_name) #:nodoc: + if base_name && primary_key_prefix_type == :table_name + base_name.foreign_key(false) + elsif base_name && primary_key_prefix_type == :table_name_with_underscore + base_name.foreign_key else - "id" + if ActiveRecord::Base != self && table_exists? + pk = connection.schema_cache.primary_keys(table_name) + suppress_composite_primary_key(pk) + else + "id" + end end end - end - # Sets the name of the primary key column. - # - # class Project < ActiveRecord::Base - # self.primary_key = 'sysid' - # end - # - # You can also define the #primary_key method yourself: - # - # class Project < ActiveRecord::Base - # def self.primary_key - # 'foo_' + super - # end - # end - # - # Project.primary_key # => "foo_id" - def primary_key=(value) - @primary_key = value && value.to_s - @quoted_primary_key = nil - @attributes_builder = nil - end + # Sets the name of the primary key column. + # + # class Project < ActiveRecord::Base + # self.primary_key = 'sysid' + # end + # + # You can also define the #primary_key method yourself: + # + # class Project < ActiveRecord::Base + # def self.primary_key + # 'foo_' + super + # end + # end + # + # Project.primary_key # => "foo_id" + def primary_key=(value) + @primary_key = value && value.to_s + @quoted_primary_key = nil + @attributes_builder = nil + end - private + private - def suppress_composite_primary_key(pk) - return pk unless pk.is_a?(Array) + def suppress_composite_primary_key(pk) + return pk unless pk.is_a?(Array) - warn <<-WARNING.strip_heredoc + warn <<-WARNING.strip_heredoc WARNING: Active Record does not support composite primary key. #{table_name} has composite primary key. Composite primary key is ignored. WARNING + end end - end end end end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index a7310130fe..95853501db 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -24,24 +24,24 @@ module ActiveRecord # to allocate an object on each call to the attribute method. # Making it frozen means that it doesn't get duped when used to # key the @attributes in read_attribute. - def define_method_attribute(name) - safe_name = name.unpack("h*".freeze).first - temp_method = "__temp__#{safe_name}" + def define_method_attribute(name) + safe_name = name.unpack("h*".freeze).first + temp_method = "__temp__#{safe_name}" - ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name + ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name - generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 + generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 def #{temp_method} name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} _read_attribute(name) { |n| missing_attribute(n, caller) } end STR - generated_attribute_methods.module_eval do - alias_method name, temp_method - undef_method temp_method + generated_attribute_methods.module_eval do + alias_method name, temp_method + undef_method temp_method + end end - end end # Returns the value of the attribute identified by <tt>attr_name</tt> after diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 65978aea2a..8fb29fb358 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -50,7 +50,7 @@ module ActiveRecord # to ensure special objects (e.g. Active Record models) are dumped correctly # using the #as_json hook. coder = if class_name_or_coder == ::JSON - Coders::JSON + Coders::JSON elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) } class_name_or_coder else 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 d7b5f8b262..ff9fd206c0 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -26,31 +26,31 @@ module ActiveRecord private - def convert_time_to_time_zone(value) - return if value.nil? + def convert_time_to_time_zone(value) + return if value.nil? - if value.acts_like?(:time) - value.in_time_zone - elsif value.is_a?(::Float) - value - else - map_avoiding_infinite_recursion(value) { |v| convert_time_to_time_zone(v) } + if value.acts_like?(:time) + value.in_time_zone + elsif value.is_a?(::Float) + value + else + map_avoiding_infinite_recursion(value) { |v| convert_time_to_time_zone(v) } + end end - end - def set_time_zone_without_conversion(value) - ::Time.zone.local_to_utc(value).in_time_zone if value - end + def set_time_zone_without_conversion(value) + ::Time.zone.local_to_utc(value).in_time_zone if value + end - def map_avoiding_infinite_recursion(value) - map(value) do |v| - if value.equal?(v) - nil - else - yield(v) + def map_avoiding_infinite_recursion(value) + map(value) do |v| + if value.equal?(v) + nil + else + yield(v) + end end end - end end extend ActiveSupport::Concern @@ -69,31 +69,31 @@ module ActiveRecord module ClassMethods private - def inherited(subclass) - # We need to apply this decorator here, rather than on module inclusion. The closure - # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the - # sub class being decorated. As such, changes to `time_zone_aware_attributes`, or - # `skip_time_zone_conversion_for_attributes` would not be picked up. - subclass.class_eval do - matcher = ->(name, type) { create_time_zone_conversion_attribute?(name, type) } - decorate_matching_attribute_types(matcher, :_time_zone_conversion) do |type| - TimeZoneConverter.new(type) + def inherited(subclass) + # We need to apply this decorator here, rather than on module inclusion. The closure + # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the + # sub class being decorated. As such, changes to `time_zone_aware_attributes`, or + # `skip_time_zone_conversion_for_attributes` would not be picked up. + subclass.class_eval do + matcher = ->(name, type) { create_time_zone_conversion_attribute?(name, type) } + decorate_matching_attribute_types(matcher, :_time_zone_conversion) do |type| + TimeZoneConverter.new(type) + end end + super end - super - end - def create_time_zone_conversion_attribute?(name, cast_type) - enabled_for_column = time_zone_aware_attributes && - !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) - result = enabled_for_column && - time_zone_aware_types.include?(cast_type.type) - - if enabled_for_column && - !result && - cast_type.type == :time && - time_zone_aware_types.include?(:not_explicitly_configured) - ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc) + def create_time_zone_conversion_attribute?(name, cast_type) + enabled_for_column = time_zone_aware_attributes && + !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) + result = enabled_for_column && + time_zone_aware_types.include?(cast_type.type) + + if enabled_for_column && + !result && + cast_type.type == :time && + time_zone_aware_types.include?(:not_explicitly_configured) + ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc) Time columns will become time zone aware in Rails 5.1. This still causes `String`s to be parsed as if they were in `Time.zone`, and `Time`s to be converted to `Time.zone`. @@ -106,10 +106,10 @@ module ActiveRecord config.active_record.time_zone_aware_types = [:datetime, :time] MESSAGE - end + end - result - end + result + end end end end diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index e7f62197c3..5822414129 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -10,11 +10,11 @@ module ActiveRecord module ClassMethods protected - def define_method_attribute=(name) - safe_name = name.unpack("h*".freeze).first - ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name + def define_method_attribute=(name) + safe_name = name.unpack("h*".freeze).first + ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name - generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 + generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 def __temp__#{safe_name}=(value) name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} write_attribute(name, value) @@ -22,7 +22,7 @@ module ActiveRecord alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= undef_method :__temp__#{safe_name}= STR - end + end end # Updates the attribute identified by <tt>attr_name</tt> with the @@ -38,22 +38,22 @@ module ActiveRecord private # Handle *= for method_missing. - def attribute=(attribute_name, value) - write_attribute(attribute_name, value) - end + def attribute=(attribute_name, value) + write_attribute(attribute_name, value) + end - def write_attribute_with_type_cast(attr_name, value, should_type_cast) - attr_name = attr_name.to_s - attr_name = self.class.primary_key if attr_name == "id" && self.class.primary_key + def write_attribute_with_type_cast(attr_name, value, should_type_cast) + attr_name = attr_name.to_s + attr_name = self.class.primary_key if attr_name == "id" && self.class.primary_key - if should_type_cast - @attributes.write_from_user(attr_name, value) - else - @attributes.write_cast_value(attr_name, value) - end + if should_type_cast + @attributes.write_from_user(attr_name, value) + else + @attributes.write_cast_value(attr_name, value) + end - value - end + value + end end end end diff --git a/activerecord/lib/active_record/attribute_mutation_tracker.rb b/activerecord/lib/active_record/attribute_mutation_tracker.rb index 0133b4d0be..c257aef52f 100644 --- a/activerecord/lib/active_record/attribute_mutation_tracker.rb +++ b/activerecord/lib/active_record/attribute_mutation_tracker.rb @@ -36,13 +36,13 @@ module ActiveRecord protected - attr_reader :attributes + attr_reader :attributes private - def attr_names - attributes.keys - end + def attr_names + attributes.keys + end end class NullMutationTracker # :nodoc: diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb index fd569d869a..5bde1f107c 100644 --- a/activerecord/lib/active_record/attribute_set.rb +++ b/activerecord/lib/active_record/attribute_set.rb @@ -100,12 +100,12 @@ module ActiveRecord protected - attr_reader :attributes + attr_reader :attributes private - def initialized_attributes - attributes.select { |_, attr| attr.initialized? } - end + def initialized_attributes + attributes.select { |_, attr| attr.initialized? } + end end end diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb index b4d98ca3c0..661f996e1a 100644 --- a/activerecord/lib/active_record/attribute_set/builder.rb +++ b/activerecord/lib/active_record/attribute_set/builder.rb @@ -92,31 +92,31 @@ module ActiveRecord protected - attr_reader :types, :values, :additional_types, :delegate_hash, :default - - def materialize - unless @materialized - values.each_key { |key| self[key] } - types.each_key { |key| self[key] } - unless frozen? - @materialized = true + attr_reader :types, :values, :additional_types, :delegate_hash, :default + + def materialize + unless @materialized + values.each_key { |key| self[key] } + types.each_key { |key| self[key] } + unless frozen? + @materialized = true + end end + delegate_hash end - delegate_hash - end private - def assign_default_value(name) - type = additional_types.fetch(name, types[name]) - value_present = true - value = values.fetch(name) { value_present = false } + def assign_default_value(name) + type = additional_types.fetch(name, types[name]) + value_present = true + value = values.fetch(name) { value_present = false } - if value_present - delegate_hash[name] = Attribute.from_database(name, value, type) - elsif types.key?(name) - delegate_hash[name] = default.call(name) || Attribute.uninitialized(name, type) + if value_present + delegate_hash[name] = Attribute.from_database(name, value, type) + elsif types.key?(name) + delegate_hash[name] = default.call(name) || Attribute.uninitialized(name, type) + end end - end end end diff --git a/activerecord/lib/active_record/attribute_set/yaml_encoder.rb b/activerecord/lib/active_record/attribute_set/yaml_encoder.rb index 52ffe719f4..c86cfc4263 100644 --- a/activerecord/lib/active_record/attribute_set/yaml_encoder.rb +++ b/activerecord/lib/active_record/attribute_set/yaml_encoder.rb @@ -33,7 +33,7 @@ module ActiveRecord protected - attr_reader :default_types + attr_reader :default_types end end end diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb index 880d9a2b84..4b92e5835f 100644 --- a/activerecord/lib/active_record/attributes.rb +++ b/activerecord/lib/active_record/attributes.rb @@ -242,24 +242,24 @@ module ActiveRecord private - NO_DEFAULT_PROVIDED = Object.new # :nodoc: - private_constant :NO_DEFAULT_PROVIDED + NO_DEFAULT_PROVIDED = Object.new # :nodoc: + private_constant :NO_DEFAULT_PROVIDED - def define_default_attribute(name, value, type, from_user:) - if value == NO_DEFAULT_PROVIDED - default_attribute = _default_attributes[name].with_type(type) - elsif from_user - default_attribute = Attribute::UserProvidedDefault.new( - name, - value, - type, - _default_attributes.fetch(name.to_s) { nil }, - ) - else - default_attribute = Attribute.from_database(name, value, type) + def define_default_attribute(name, value, type, from_user:) + if value == NO_DEFAULT_PROVIDED + default_attribute = _default_attributes[name].with_type(type) + elsif from_user + default_attribute = Attribute::UserProvidedDefault.new( + name, + value, + type, + _default_attributes.fetch(name.to_s) { nil }, + ) + else + default_attribute = Attribute.from_database(name, value, type) + end + _default_attributes[name] = default_attribute end - _default_attributes[name] = default_attribute - end end end end diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb index 8b98d9ccd7..ee38172742 100644 --- a/activerecord/lib/active_record/coders/yaml_column.rb +++ b/activerecord/lib/active_record/coders/yaml_column.rb @@ -39,13 +39,13 @@ module ActiveRecord private - def check_arity_of_constructor - begin - load(nil) - rescue ArgumentError - raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor." + def check_arity_of_constructor + begin + load(nil) + rescue ArgumentError + raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor." + end end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index db798bc303..535d79b525 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -150,61 +150,61 @@ module ActiveRecord private - def internal_poll(timeout) - no_wait_poll || (timeout && wait_poll(timeout)) - end + def internal_poll(timeout) + no_wait_poll || (timeout && wait_poll(timeout)) + end - def synchronize(&block) - @lock.synchronize(&block) - end + def synchronize(&block) + @lock.synchronize(&block) + end # Test if the queue currently contains any elements. - def any? - !@queue.empty? - end + def any? + !@queue.empty? + end # A thread can remove an element from the queue without # waiting if and only if the number of currently available # connections is strictly greater than the number of waiting # threads. - def can_remove_no_wait? - @queue.size > @num_waiting - end + def can_remove_no_wait? + @queue.size > @num_waiting + end # Removes and returns the head of the queue if possible, or nil. - def remove - @queue.shift - end + def remove + @queue.shift + end # Remove and return the head the queue if the number of # available elements is strictly greater than the number of # threads currently waiting. Otherwise, return nil. - def no_wait_poll - remove if can_remove_no_wait? - end + def no_wait_poll + remove if can_remove_no_wait? + end # Waits on the queue up to +timeout+ seconds, then removes and # returns the head of the queue. - def wait_poll(timeout) - @num_waiting += 1 + def wait_poll(timeout) + @num_waiting += 1 - t0 = Time.now - elapsed = 0 - loop do - @cond.wait(timeout - elapsed) + t0 = Time.now + elapsed = 0 + loop do + @cond.wait(timeout - elapsed) - return remove if any? + return remove if any? - elapsed = Time.now - t0 - if elapsed >= timeout - msg = "could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use" % - [timeout, elapsed] - raise ConnectionTimeoutError, msg + elapsed = Time.now - t0 + if elapsed >= timeout + msg = "could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use" % + [timeout, elapsed] + raise ConnectionTimeoutError, msg + end end + ensure + @num_waiting -= 1 end - ensure - @num_waiting -= 1 - end end # Adds the ability to turn a basic fair FIFO queue into one @@ -274,11 +274,11 @@ module ActiveRecord include BiasableQueue private - def internal_poll(timeout) - conn = super - conn.lease if conn - conn - end + def internal_poll(timeout) + conn = super + conn.lease if conn + conn + end end # Every +frequency+ seconds, the reaper will call +reap+ on +pool+. @@ -584,111 +584,111 @@ module ActiveRecord private #-- # this is unfortunately not concurrent - def bulk_make_new_connections(num_new_conns_needed) - num_new_conns_needed.times do - # try_to_checkout_new_connection will not exceed pool's @size limit - if new_conn = try_to_checkout_new_connection - # make the new_conn available to the starving threads stuck @available Queue - checkin(new_conn) + def bulk_make_new_connections(num_new_conns_needed) + num_new_conns_needed.times do + # try_to_checkout_new_connection will not exceed pool's @size limit + if new_conn = try_to_checkout_new_connection + # make the new_conn available to the starving threads stuck @available Queue + checkin(new_conn) + end end end - end #-- # From the discussion on GitHub: # https://github.com/rails/rails/pull/14938#commitcomment-6601951 # This hook-in method allows for easier monkey-patching fixes needed by # JRuby users that use Fibers. - def connection_cache_key(thread) - thread - end + def connection_cache_key(thread) + thread + end # Take control of all existing connections so a "group" action such as # reload/disconnect can be performed safely. It is no longer enough to # wrap it in +synchronize+ because some pool's actions are allowed # to be performed outside of the main +synchronize+ block. - def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true) - with_new_connections_blocked do - attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout) - yield + def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true) + with_new_connections_blocked do + attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout) + yield + end end - end - def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true) - collected_conns = synchronize do - # account for our own connections - @connections.select {|conn| conn.owner == Thread.current} - end + def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true) + collected_conns = synchronize do + # account for our own connections + @connections.select {|conn| conn.owner == Thread.current} + end - newly_checked_out = [] - timeout_time = Time.now + (@checkout_timeout * 2) + newly_checked_out = [] + timeout_time = Time.now + (@checkout_timeout * 2) - @available.with_a_bias_for(Thread.current) do - loop do - synchronize do - return if collected_conns.size == @connections.size && @now_connecting == 0 - remaining_timeout = timeout_time - Time.now - remaining_timeout = 0 if remaining_timeout < 0 - conn = checkout_for_exclusive_access(remaining_timeout) - collected_conns << conn - newly_checked_out << conn + @available.with_a_bias_for(Thread.current) do + loop do + synchronize do + return if collected_conns.size == @connections.size && @now_connecting == 0 + remaining_timeout = timeout_time - Time.now + remaining_timeout = 0 if remaining_timeout < 0 + conn = checkout_for_exclusive_access(remaining_timeout) + collected_conns << conn + newly_checked_out << conn + end end end - end - rescue ExclusiveConnectionTimeoutError - # <tt>raise_on_acquisition_timeout == false</tt> means we are directed to ignore any - # timeouts and are expected to just give up: we've obtained as many connections - # as possible, note that in a case like that we don't return any of the - # +newly_checked_out+ connections. - - if raise_on_acquisition_timeout + rescue ExclusiveConnectionTimeoutError + # <tt>raise_on_acquisition_timeout == false</tt> means we are directed to ignore any + # timeouts and are expected to just give up: we've obtained as many connections + # as possible, note that in a case like that we don't return any of the + # +newly_checked_out+ connections. + + if raise_on_acquisition_timeout + release_newly_checked_out = true + raise + end + rescue Exception # if something else went wrong + # this can't be a "naked" rescue, because we have should return conns + # even for non-StandardErrors release_newly_checked_out = true raise + ensure + if release_newly_checked_out && newly_checked_out + # releasing only those conns that were checked out in this method, conns + # checked outside this method (before it was called) are not for us to release + newly_checked_out.each {|conn| checkin(conn)} + end end - rescue Exception # if something else went wrong - # this can't be a "naked" rescue, because we have should return conns - # even for non-StandardErrors - release_newly_checked_out = true - raise - ensure - if release_newly_checked_out && newly_checked_out - # releasing only those conns that were checked out in this method, conns - # checked outside this method (before it was called) are not for us to release - newly_checked_out.each {|conn| checkin(conn)} - end - end #-- # Must be called in a synchronize block. - def checkout_for_exclusive_access(checkout_timeout) - checkout(checkout_timeout) - rescue ConnectionTimeoutError - # this block can't be easily moved into attempt_to_checkout_all_existing_connections's - # rescue block, because doing so would put it outside of synchronize section, without - # being in a critical section thread_report might become inaccurate - msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds" - - thread_report = [] - @connections.each do |conn| - unless conn.owner == Thread.current - thread_report << "#{conn} is owned by #{conn.owner}" + def checkout_for_exclusive_access(checkout_timeout) + checkout(checkout_timeout) + rescue ConnectionTimeoutError + # this block can't be easily moved into attempt_to_checkout_all_existing_connections's + # rescue block, because doing so would put it outside of synchronize section, without + # being in a critical section thread_report might become inaccurate + msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds" + + thread_report = [] + @connections.each do |conn| + unless conn.owner == Thread.current + thread_report << "#{conn} is owned by #{conn.owner}" + end end - end - msg << " (#{thread_report.join(', ')})" if thread_report.any? + msg << " (#{thread_report.join(', ')})" if thread_report.any? - raise ExclusiveConnectionTimeoutError, msg - end + raise ExclusiveConnectionTimeoutError, msg + end - def with_new_connections_blocked - previous_value = nil - synchronize do - previous_value, @new_cons_enabled = @new_cons_enabled, false + def with_new_connections_blocked + previous_value = nil + synchronize do + previous_value, @new_cons_enabled = @new_cons_enabled, false + end + yield + ensure + synchronize { @new_cons_enabled = previous_value } end - yield - ensure - synchronize { @new_cons_enabled = previous_value } - end # Acquire a connection by one of 1) immediately removing one # from the queue of available connections, 2) creating a new @@ -701,86 +701,86 @@ module ActiveRecord #-- # Implementation detail: the connection returned by +acquire_connection+ # will already be "+connection.lease+ -ed" to the current thread. - def acquire_connection(checkout_timeout) - # NOTE: we rely on +@available.poll+ and +try_to_checkout_new_connection+ to - # +conn.lease+ the returned connection (and to do this in a +synchronized+ - # section). This is not the cleanest implementation, as ideally we would - # <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to +@available.poll+ - # and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections - # of the said methods and avoid an additional +synchronize+ overhead. - if conn = @available.poll || try_to_checkout_new_connection - conn - else - reap - @available.poll(checkout_timeout) + def acquire_connection(checkout_timeout) + # NOTE: we rely on +@available.poll+ and +try_to_checkout_new_connection+ to + # +conn.lease+ the returned connection (and to do this in a +synchronized+ + # section). This is not the cleanest implementation, as ideally we would + # <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to +@available.poll+ + # and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections + # of the said methods and avoid an additional +synchronize+ overhead. + if conn = @available.poll || try_to_checkout_new_connection + conn + else + reap + @available.poll(checkout_timeout) + end end - end #-- # if owner_thread param is omitted, this must be called in synchronize block - def remove_connection_from_thread_cache(conn, owner_thread = conn.owner) - @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn) - end - alias_method :release, :remove_connection_from_thread_cache + def remove_connection_from_thread_cache(conn, owner_thread = conn.owner) + @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn) + end + alias_method :release, :remove_connection_from_thread_cache - def new_connection - Base.send(spec.adapter_method, spec.config).tap do |conn| - conn.schema_cache = schema_cache.dup if schema_cache + def new_connection + Base.send(spec.adapter_method, spec.config).tap do |conn| + conn.schema_cache = schema_cache.dup if schema_cache + end end - end # If the pool is not at a +@size+ limit, establish new connection. Connecting # to the DB is done outside main synchronized section. #-- # Implementation constraint: a newly established connection returned by this # method must be in the +.leased+ state. - def try_to_checkout_new_connection - # first in synchronized section check if establishing new conns is allowed - # and increment @now_connecting, to prevent overstepping this pool's @size - # constraint - do_checkout = synchronize do - if @new_cons_enabled && (@connections.size + @now_connecting) < @size - @now_connecting += 1 - end - end - if do_checkout - begin - # if successfully incremented @now_connecting establish new connection - # outside of synchronized section - conn = checkout_new_connection - ensure - synchronize do - if conn - adopt_connection(conn) - # returned conn needs to be already leased - conn.lease + def try_to_checkout_new_connection + # first in synchronized section check if establishing new conns is allowed + # and increment @now_connecting, to prevent overstepping this pool's @size + # constraint + do_checkout = synchronize do + if @new_cons_enabled && (@connections.size + @now_connecting) < @size + @now_connecting += 1 + end + end + if do_checkout + begin + # if successfully incremented @now_connecting establish new connection + # outside of synchronized section + conn = checkout_new_connection + ensure + synchronize do + if conn + adopt_connection(conn) + # returned conn needs to be already leased + conn.lease + end + @now_connecting -= 1 end - @now_connecting -= 1 end end end - end - def adopt_connection(conn) - conn.pool = self - @connections << conn - end + def adopt_connection(conn) + conn.pool = self + @connections << conn + end - def checkout_new_connection - raise ConnectionNotEstablished unless @automatic_reconnect - new_connection - end + def checkout_new_connection + raise ConnectionNotEstablished unless @automatic_reconnect + new_connection + end - def checkout_and_verify(c) - c._run_checkout_callbacks do - c.verify! + def checkout_and_verify(c) + c._run_checkout_callbacks do + c.verify! + end + c + rescue + remove c + c.disconnect! + raise end - c - rescue - remove c - c.disconnect! - raise - end end # ConnectionHandler is a collection of ConnectionPool objects. It is used @@ -942,14 +942,14 @@ module ActiveRecord private - def owner_to_pool - @owner_to_pool[Process.pid] - end + def owner_to_pool + @owner_to_pool[Process.pid] + end - def pool_from_any_process_for(spec_name) - owner_to_pool = @owner_to_pool.values.find { |v| v[spec_name] } - owner_to_pool && owner_to_pool[spec_name] - end + def pool_from_any_process_for(spec_name) + owner_to_pool = @owner_to_pool.values.find { |v| v[spec_name] } + owner_to_pool && owner_to_pool[spec_name] + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index 47f24df22f..10c60080d5 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -73,23 +73,23 @@ module ActiveRecord private - def cache_sql(sql, binds) - result = - if @query_cache[sql].key?(binds) - ActiveSupport::Notifications.instrument("sql.active_record", - sql: sql, binds: binds, name: "CACHE", connection_id: object_id) - @query_cache[sql][binds] - else - @query_cache[sql][binds] = yield - end - result.dup - end + def cache_sql(sql, binds) + result = + if @query_cache[sql].key?(binds) + ActiveSupport::Notifications.instrument("sql.active_record", + sql: sql, binds: binds, name: "CACHE", connection_id: object_id) + @query_cache[sql][binds] + else + @query_cache[sql][binds] = yield + end + result.dup + end # If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such # queries should not be cached. - def locked?(arel) - arel.respond_to?(:locked) && arel.locked - end + def locked?(arel) + arel.respond_to?(:locked) && arel.locked + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 20d009bbf8..bbd52b8a91 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -152,47 +152,47 @@ module ActiveRecord private - def type_casted_binds(binds) - binds.map { |attr| type_cast(attr.value_for_database) } - end - - def types_which_need_no_typecasting - [nil, Numeric, String] - end - - def _quote(value) - case value - when String, ActiveSupport::Multibyte::Chars, Type::Binary::Data - "'#{quote_string(value.to_s)}'" - when true then quoted_true - when false then quoted_false - when nil then "NULL" - # BigDecimals need to be put in a non-normalized form and quoted. - when BigDecimal then value.to_s("F") - when Numeric, ActiveSupport::Duration then value.to_s - when Type::Time::Value then "'#{quoted_time(value)}'" - when Date, Time then "'#{quoted_date(value)}'" - when Symbol then "'#{quote_string(value.to_s)}'" - when Class then "'#{value}'" - else raise TypeError, "can't quote #{value.class.name}" + def type_casted_binds(binds) + binds.map { |attr| type_cast(attr.value_for_database) } end - end - def _type_cast(value) - case value - when Symbol, ActiveSupport::Multibyte::Chars, Type::Binary::Data - value.to_s - when true then unquoted_true - when false then unquoted_false - # BigDecimals need to be put in a non-normalized form and quoted. - when BigDecimal then value.to_s("F") - when Type::Time::Value then quoted_time(value) - when Date, Time then quoted_date(value) - when *types_which_need_no_typecasting - value - else raise TypeError + def types_which_need_no_typecasting + [nil, Numeric, String] + end + + def _quote(value) + case value + when String, ActiveSupport::Multibyte::Chars, Type::Binary::Data + "'#{quote_string(value.to_s)}'" + when true then quoted_true + when false then quoted_false + when nil then "NULL" + # BigDecimals need to be put in a non-normalized form and quoted. + when BigDecimal then value.to_s("F") + when Numeric, ActiveSupport::Duration then value.to_s + when Type::Time::Value then "'#{quoted_time(value)}'" + when Date, Time then "'#{quoted_date(value)}'" + when Symbol then "'#{quote_string(value.to_s)}'" + when Class then "'#{value}'" + else raise TypeError, "can't quote #{value.class.name}" + end + end + + def _type_cast(value) + case value + when Symbol, ActiveSupport::Multibyte::Chars, Type::Binary::Data + value.to_s + when true then unquoted_true + when false then unquoted_false + # BigDecimals need to be put in a non-normalized form and quoted. + when BigDecimal then value.to_s("F") + when Type::Time::Value then quoted_time(value) + when Date, Time then quoted_date(value) + when *types_which_need_no_typecasting + value + else raise TypeError + end end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index c1fc99ce1f..1d18a58eab 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -61,9 +61,9 @@ module ActiveRecord end private - def default_primary_key - "id" - end + def default_primary_key + "id" + end end class ReferenceDefinition # :nodoc: @@ -103,51 +103,51 @@ module ActiveRecord protected - attr_reader :name, :polymorphic, :index, :foreign_key, :type, :options + attr_reader :name, :polymorphic, :index, :foreign_key, :type, :options private - def as_options(value, default = {}) - if value.is_a?(Hash) - value - else - default + def as_options(value, default = {}) + if value.is_a?(Hash) + value + else + default + end end - end - def polymorphic_options - as_options(polymorphic, options) - end + def polymorphic_options + as_options(polymorphic, options) + end - def index_options - as_options(index) - end + def index_options + as_options(index) + end - def foreign_key_options - as_options(foreign_key).merge(column: column_name) - end + def foreign_key_options + as_options(foreign_key).merge(column: column_name) + end - def columns - result = [[column_name, type, options]] - if polymorphic - result.unshift(["#{name}_type", :string, polymorphic_options]) + def columns + result = [[column_name, type, options]] + if polymorphic + result.unshift(["#{name}_type", :string, polymorphic_options]) + end + result end - result - end - def column_name - "#{name}_id" - end + def column_name + "#{name}_id" + end - def column_names - columns.map(&:first) - end + def column_names + columns.map(&:first) + end - def foreign_table_name - foreign_key_options.fetch(:to_table) do - Base.pluralize_table_names ? name.to_s.pluralize : name + def foreign_table_name + foreign_key_options.fetch(:to_table) do + Base.pluralize_table_names ? name.to_s.pluralize : name + end end - end end module ColumnMethods @@ -383,13 +383,13 @@ module ActiveRecord end private - def create_column_definition(name, type) - ColumnDefinition.new name, type - end + def create_column_definition(name, type) + ColumnDefinition.new name, type + end - def aliased_types(name, fallback) - "timestamp" == name ? :datetime : fallback - end + def aliased_types(name, fallback) + "timestamp" == name ? :datetime : fallback + end end class AlterTable # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb index 321e08c5ca..8bb7362c2e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -58,48 +58,48 @@ module ActiveRecord private - def default_primary_key?(column) - schema_type(column) == :integer - end + def default_primary_key?(column) + schema_type(column) == :integer + end - def schema_type(column) - if column.bigint? - :bigint - else - column.type + def schema_type(column) + if column.bigint? + :bigint + else + column.type + end end - end - def schema_limit(column) - limit = column.limit unless column.bigint? - limit.inspect if limit && limit != native_database_types[column.type][:limit] - end + def schema_limit(column) + limit = column.limit unless column.bigint? + limit.inspect if limit && limit != native_database_types[column.type][:limit] + end - def schema_precision(column) - column.precision.inspect if column.precision - end + def schema_precision(column) + column.precision.inspect if column.precision + end - def schema_scale(column) - column.scale.inspect if column.scale - end + def schema_scale(column) + column.scale.inspect if column.scale + end - def schema_default(column) - type = lookup_cast_type_from_column(column) - default = type.deserialize(column.default) - if default.nil? - schema_expression(column) - else - type.type_cast_for_schema(default) + def schema_default(column) + type = lookup_cast_type_from_column(column) + default = type.deserialize(column.default) + if default.nil? + schema_expression(column) + else + type.type_cast_for_schema(default) + end end - end - def schema_expression(column) - "-> { #{column.default_function.inspect} }" if column.default_function - end + def schema_expression(column) + "-> { #{column.default_function.inspect} }" if column.default_function + end - def schema_collation(column) - column.collation.inspect if column.collation - end + def schema_collation(column) + column.collation.inspect if column.collation + end end end end 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 f2134fe035..d2ebc36fff 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -1245,49 +1245,49 @@ module ActiveRecord end private - def create_table_definition(*args) - TableDefinition.new(*args) - end - - def create_alter_table(name) - AlterTable.new create_table_definition(name) - end + def create_table_definition(*args) + TableDefinition.new(*args) + end - def index_name_options(column_names) # :nodoc: - if column_names.is_a?(String) - column_names = column_names.scan(/\w+/).join("_") + def create_alter_table(name) + AlterTable.new create_table_definition(name) end - { column: column_names } - end + def index_name_options(column_names) # :nodoc: + if column_names.is_a?(String) + column_names = column_names.scan(/\w+/).join("_") + end - def foreign_key_name(table_name, options) # :nodoc: - identifier = "#{table_name}_#{options.fetch(:column)}_fk" - hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10) - options.fetch(:name) do - "fk_rails_#{hashed_identifier}" + { column: column_names } + end + + def foreign_key_name(table_name, options) # :nodoc: + identifier = "#{table_name}_#{options.fetch(:column)}_fk" + hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10) + options.fetch(:name) do + "fk_rails_#{hashed_identifier}" + end end - end - def validate_index_length!(table_name, new_name, internal = false) # :nodoc: - max_index_length = internal ? index_name_length : allowed_index_name_length + def validate_index_length!(table_name, new_name, internal = false) # :nodoc: + max_index_length = internal ? index_name_length : allowed_index_name_length - if new_name.length > max_index_length - raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters" + if new_name.length > max_index_length + raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters" + end end - end - def extract_new_default_value(default_or_changes) - if default_or_changes.is_a?(Hash) && default_or_changes.has_key?(:from) && default_or_changes.has_key?(:to) - default_or_changes[:to] - else - default_or_changes + def extract_new_default_value(default_or_changes) + if default_or_changes.is_a?(Hash) && default_or_changes.has_key?(:from) && default_or_changes.has_key?(:to) + default_or_changes[:to] + else + default_or_changes + end end - end - def can_remove_index_by_name?(options) - options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty? - end + def can_remove_index_by_name?(options) + options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty? + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 7ba3b5f43b..8a2a1fafb1 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -497,115 +497,115 @@ module ActiveRecord protected - def initialize_type_map(m) # :nodoc: - register_class_with_limit m, %r(boolean)i, Type::Boolean - register_class_with_limit m, %r(char)i, Type::String - register_class_with_limit m, %r(binary)i, Type::Binary - register_class_with_limit m, %r(text)i, Type::Text - register_class_with_precision m, %r(date)i, Type::Date - register_class_with_precision m, %r(time)i, Type::Time - register_class_with_precision m, %r(datetime)i, Type::DateTime - register_class_with_limit m, %r(float)i, Type::Float - register_class_with_limit m, %r(int)i, Type::Integer - - m.alias_type %r(blob)i, "binary" - m.alias_type %r(clob)i, "text" - m.alias_type %r(timestamp)i, "datetime" - m.alias_type %r(numeric)i, "decimal" - m.alias_type %r(number)i, "decimal" - m.alias_type %r(double)i, "float" - - m.register_type(%r(decimal)i) do |sql_type| - scale = extract_scale(sql_type) - precision = extract_precision(sql_type) - - if scale == 0 - # FIXME: Remove this class as well - Type::DecimalWithoutScale.new(precision: precision) - else - Type::Decimal.new(precision: precision, scale: scale) + def initialize_type_map(m) # :nodoc: + register_class_with_limit m, %r(boolean)i, Type::Boolean + register_class_with_limit m, %r(char)i, Type::String + register_class_with_limit m, %r(binary)i, Type::Binary + register_class_with_limit m, %r(text)i, Type::Text + register_class_with_precision m, %r(date)i, Type::Date + register_class_with_precision m, %r(time)i, Type::Time + register_class_with_precision m, %r(datetime)i, Type::DateTime + register_class_with_limit m, %r(float)i, Type::Float + register_class_with_limit m, %r(int)i, Type::Integer + + m.alias_type %r(blob)i, "binary" + m.alias_type %r(clob)i, "text" + m.alias_type %r(timestamp)i, "datetime" + m.alias_type %r(numeric)i, "decimal" + m.alias_type %r(number)i, "decimal" + m.alias_type %r(double)i, "float" + + m.register_type(%r(decimal)i) do |sql_type| + scale = extract_scale(sql_type) + precision = extract_precision(sql_type) + + if scale == 0 + # FIXME: Remove this class as well + Type::DecimalWithoutScale.new(precision: precision) + else + Type::Decimal.new(precision: precision, scale: scale) + end end end - end - - def reload_type_map # :nodoc: - type_map.clear - initialize_type_map(type_map) - end - def register_class_with_limit(mapping, key, klass) # :nodoc: - mapping.register_type(key) do |*args| - limit = extract_limit(args.last) - klass.new(limit: limit) + def reload_type_map # :nodoc: + type_map.clear + initialize_type_map(type_map) end - end - def register_class_with_precision(mapping, key, klass) # :nodoc: - mapping.register_type(key) do |*args| - precision = extract_precision(args.last) - klass.new(precision: precision) + def register_class_with_limit(mapping, key, klass) # :nodoc: + mapping.register_type(key) do |*args| + limit = extract_limit(args.last) + klass.new(limit: limit) + end end - end - def extract_scale(sql_type) # :nodoc: - case sql_type - when /\((\d+)\)/ then 0 - when /\((\d+)(,(\d+))\)/ then $3.to_i + def register_class_with_precision(mapping, key, klass) # :nodoc: + mapping.register_type(key) do |*args| + precision = extract_precision(args.last) + klass.new(precision: precision) + end end - end - def extract_precision(sql_type) # :nodoc: - $1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/ - end + def extract_scale(sql_type) # :nodoc: + case sql_type + when /\((\d+)\)/ then 0 + when /\((\d+)(,(\d+))\)/ then $3.to_i + end + end - def extract_limit(sql_type) # :nodoc: - case sql_type - when /^bigint/i - 8 - when /\((.*)\)/ - $1.to_i + def extract_precision(sql_type) # :nodoc: + $1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/ end - end - def translate_exception_class(e, sql) - begin - message = "#{e.class.name}: #{e.message}: #{sql}" - rescue Encoding::CompatibilityError - message = "#{e.class.name}: #{e.message.force_encoding sql.encoding}: #{sql}" + def extract_limit(sql_type) # :nodoc: + case sql_type + when /^bigint/i + 8 + when /\((.*)\)/ + $1.to_i + end end - exception = translate_exception(e, message) - exception.set_backtrace e.backtrace - exception - end + def translate_exception_class(e, sql) + begin + message = "#{e.class.name}: #{e.message}: #{sql}" + rescue Encoding::CompatibilityError + message = "#{e.class.name}: #{e.message.force_encoding sql.encoding}: #{sql}" + end - def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil) - @instrumenter.instrument( - "sql.active_record", - sql: sql, - name: name, - binds: binds, - type_casted_binds: type_casted_binds, - statement_name: statement_name, - connection_id: object_id) { yield } - rescue => e - raise translate_exception_class(e, sql) - end + exception = translate_exception(e, message) + exception.set_backtrace e.backtrace + exception + end - def translate_exception(exception, message) - # override in derived class - ActiveRecord::StatementInvalid.new(message) - end + def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil) + @instrumenter.instrument( + "sql.active_record", + sql: sql, + name: name, + binds: binds, + type_casted_binds: type_casted_binds, + statement_name: statement_name, + connection_id: object_id) { yield } + rescue => e + raise translate_exception_class(e, sql) + end - def without_prepared_statement?(binds) - !prepared_statements || binds.empty? - end + def translate_exception(exception, message) + # override in derived class + ActiveRecord::StatementInvalid.new(message) + end - def column_for(table_name, column_name) # :nodoc: - column_name = column_name.to_s - columns(table_name).detect { |c| c.name == column_name } || - raise(ActiveRecordError, "No such column: #{table_name}.#{column_name}") - end + def without_prepared_statement?(binds) + !prepared_statements || binds.empty? + end + + def column_for(table_name, column_name) # :nodoc: + column_name = column_name.to_s + columns(table_name).detect { |c| c.name == column_name } || + raise(ActiveRecordError, "No such column: #{table_name}.#{column_name}") + end end end end 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 8ac8996d21..bea8d8dda6 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -651,340 +651,340 @@ module ActiveRecord protected - def initialize_type_map(m) # :nodoc: - super - - register_class_with_limit m, %r(char)i, MysqlString - - m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1) - m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1) - m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1) - m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1) - m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1) - m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1) - m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1) - m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1) - m.register_type %r(^float)i, Type::Float.new(limit: 24) - m.register_type %r(^double)i, Type::Float.new(limit: 53) - m.register_type %r(^json)i, MysqlJson.new - - register_integer_type m, %r(^bigint)i, limit: 8 - register_integer_type m, %r(^int)i, limit: 4 - register_integer_type m, %r(^mediumint)i, limit: 3 - register_integer_type m, %r(^smallint)i, limit: 2 - register_integer_type m, %r(^tinyint)i, limit: 1 - - m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans - m.alias_type %r(year)i, "integer" - m.alias_type %r(bit)i, "binary" - - m.register_type(%r(enum)i) do |sql_type| - limit = sql_type[/^enum\((.+)\)/i, 1] - .split(",").map{|enum| enum.strip.length - 2}.max - MysqlString.new(limit: limit) - end - - m.register_type(%r(^set)i) do |sql_type| - limit = sql_type[/^set\((.+)\)/i, 1] - .split(",").map{|set| set.strip.length - 1}.sum - 1 - MysqlString.new(limit: limit) - end - end - - def register_integer_type(mapping, key, options) # :nodoc: - mapping.register_type(key) do |sql_type| - if /\bunsigned\z/ === sql_type - Type::UnsignedInteger.new(options) - else - Type::Integer.new(options) + def initialize_type_map(m) # :nodoc: + super + + register_class_with_limit m, %r(char)i, MysqlString + + m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1) + m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1) + m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1) + m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1) + m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1) + m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1) + m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1) + m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1) + m.register_type %r(^float)i, Type::Float.new(limit: 24) + m.register_type %r(^double)i, Type::Float.new(limit: 53) + m.register_type %r(^json)i, MysqlJson.new + + register_integer_type m, %r(^bigint)i, limit: 8 + register_integer_type m, %r(^int)i, limit: 4 + register_integer_type m, %r(^mediumint)i, limit: 3 + register_integer_type m, %r(^smallint)i, limit: 2 + register_integer_type m, %r(^tinyint)i, limit: 1 + + m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans + m.alias_type %r(year)i, "integer" + m.alias_type %r(bit)i, "binary" + + m.register_type(%r(enum)i) do |sql_type| + limit = sql_type[/^enum\((.+)\)/i, 1] + .split(",").map{|enum| enum.strip.length - 2}.max + MysqlString.new(limit: limit) end - end - end - def extract_precision(sql_type) - if /time/ === sql_type - super || 0 - else - super + m.register_type(%r(^set)i) do |sql_type| + limit = sql_type[/^set\((.+)\)/i, 1] + .split(",").map{|set| set.strip.length - 1}.sum - 1 + MysqlString.new(limit: limit) + end end - end - def fetch_type_metadata(sql_type, extra = "") - MySQL::TypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?) - end + def register_integer_type(mapping, key, options) # :nodoc: + mapping.register_type(key) do |sql_type| + if /\bunsigned\z/ === sql_type + Type::UnsignedInteger.new(options) + else + Type::Integer.new(options) + end + end + end - def add_index_length(option_strings, column_names, options = {}) - if options.is_a?(Hash) && length = options[:length] - case length - when Hash - column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?} - when Integer - column_names.each {|name| option_strings[name] += "(#{length})"} + def extract_precision(sql_type) + if /time/ === sql_type + super || 0 + else + super end end - return option_strings - end + def fetch_type_metadata(sql_type, extra = "") + MySQL::TypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?) + end - def quoted_columns_for_index(column_names, options = {}) - option_strings = Hash[column_names.map {|name| [name, ""]}] + def add_index_length(option_strings, column_names, options = {}) + if options.is_a?(Hash) && length = options[:length] + case length + when Hash + column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?} + when Integer + column_names.each {|name| option_strings[name] += "(#{length})"} + end + end - # add index length - option_strings = add_index_length(option_strings, column_names, options) + return option_strings + end - # add index sort order - option_strings = add_index_sort_order(option_strings, column_names, options) + def quoted_columns_for_index(column_names, options = {}) + option_strings = Hash[column_names.map {|name| [name, ""]}] - column_names.map {|name| quote_column_name(name) + option_strings[name]} - end + # add index length + option_strings = add_index_length(option_strings, column_names, options) + + # add index sort order + option_strings = add_index_sort_order(option_strings, column_names, options) + + column_names.map {|name| quote_column_name(name) + option_strings[name]} + end # See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html - ER_DUP_ENTRY = 1062 - ER_NO_REFERENCED_ROW_2 = 1452 - ER_DATA_TOO_LONG = 1406 - ER_LOCK_DEADLOCK = 1213 - - def translate_exception(exception, message) - case error_number(exception) - when ER_DUP_ENTRY - RecordNotUnique.new(message) - when ER_NO_REFERENCED_ROW_2 - InvalidForeignKey.new(message) - when ER_DATA_TOO_LONG - ValueTooLong.new(message) - when ER_LOCK_DEADLOCK - Deadlocked.new(message) - else - super + ER_DUP_ENTRY = 1062 + ER_NO_REFERENCED_ROW_2 = 1452 + ER_DATA_TOO_LONG = 1406 + ER_LOCK_DEADLOCK = 1213 + + def translate_exception(exception, message) + case error_number(exception) + when ER_DUP_ENTRY + RecordNotUnique.new(message) + when ER_NO_REFERENCED_ROW_2 + InvalidForeignKey.new(message) + when ER_DATA_TOO_LONG + ValueTooLong.new(message) + when ER_LOCK_DEADLOCK + Deadlocked.new(message) + else + super + end end - end - def add_column_sql(table_name, column_name, type, options = {}) - td = create_table_definition(table_name) - cd = td.new_column_definition(column_name, type, options) - schema_creation.accept(AddColumnDefinition.new(cd)) - end + def add_column_sql(table_name, column_name, type, options = {}) + td = create_table_definition(table_name) + cd = td.new_column_definition(column_name, type, options) + schema_creation.accept(AddColumnDefinition.new(cd)) + end - def change_column_sql(table_name, column_name, type, options = {}) - column = column_for(table_name, column_name) + def change_column_sql(table_name, column_name, type, options = {}) + column = column_for(table_name, column_name) - unless options_include_default?(options) - options[:default] = column.default - end + unless options_include_default?(options) + options[:default] = column.default + end - unless options.has_key?(:null) - options[:null] = column.null - end + unless options.has_key?(:null) + options[:null] = column.null + end - td = create_table_definition(table_name) - cd = td.new_column_definition(column.name, type, options) - schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) - end + td = create_table_definition(table_name) + cd = td.new_column_definition(column.name, type, options) + schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) + end - def rename_column_sql(table_name, column_name, new_column_name) - column = column_for(table_name, column_name) - options = { - default: column.default, - null: column.null, - auto_increment: column.auto_increment? - } + def rename_column_sql(table_name, column_name, new_column_name) + column = column_for(table_name, column_name) + options = { + default: column.default, + null: column.null, + auto_increment: column.auto_increment? + } - current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", "SCHEMA")["Type"] - td = create_table_definition(table_name) - cd = td.new_column_definition(new_column_name, current_type, options) - schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) - end + current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", "SCHEMA")["Type"] + td = create_table_definition(table_name) + cd = td.new_column_definition(new_column_name, current_type, options) + schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) + end - def remove_column_sql(table_name, column_name, type = nil, options = {}) - "DROP #{quote_column_name(column_name)}" - end + def remove_column_sql(table_name, column_name, type = nil, options = {}) + "DROP #{quote_column_name(column_name)}" + end - def remove_columns_sql(table_name, *column_names) - column_names.map {|column_name| remove_column_sql(table_name, column_name) } - end + def remove_columns_sql(table_name, *column_names) + column_names.map {|column_name| remove_column_sql(table_name, column_name) } + end - def add_index_sql(table_name, column_name, options = {}) - index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options) - index_algorithm[0, 0] = ", " if index_algorithm.present? - "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}" - end + def add_index_sql(table_name, column_name, options = {}) + index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options) + index_algorithm[0, 0] = ", " if index_algorithm.present? + "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}" + end - def remove_index_sql(table_name, options = {}) - index_name = index_name_for_remove(table_name, options) - "DROP INDEX #{index_name}" - end + def remove_index_sql(table_name, options = {}) + index_name = index_name_for_remove(table_name, options) + "DROP INDEX #{index_name}" + end - def add_timestamps_sql(table_name, options = {}) - [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)] - end + def add_timestamps_sql(table_name, options = {}) + [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)] + end - def remove_timestamps_sql(table_name, options = {}) - [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)] - end + def remove_timestamps_sql(table_name, options = {}) + [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)] + end private # MySQL is too stupid to create a temporary table for use subquery, so we have # to give it some prompting in the form of a subsubquery. Ugh! - def subquery_for(key, select) - subsubselect = select.clone - subsubselect.projections = [key] + def subquery_for(key, select) + subsubselect = select.clone + subsubselect.projections = [key] - # Materialize subquery by adding distinct - # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on' - subsubselect.distinct unless select.limit || select.offset || select.orders.any? + # Materialize subquery by adding distinct + # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on' + subsubselect.distinct unless select.limit || select.offset || select.orders.any? - subselect = Arel::SelectManager.new(select.engine) - subselect.project Arel.sql(key.name) - subselect.from subsubselect.as("__active_record_temp") - end + subselect = Arel::SelectManager.new(select.engine) + subselect.project Arel.sql(key.name) + subselect.from subsubselect.as("__active_record_temp") + end - def supports_rename_index? - mariadb? ? false : version >= "5.7.6" - end + def supports_rename_index? + mariadb? ? false : version >= "5.7.6" + end - def configure_connection - variables = @config.fetch(:variables, {}).stringify_keys + def configure_connection + variables = @config.fetch(:variables, {}).stringify_keys - # By default, MySQL 'where id is null' selects the last inserted id; Turn this off. - variables["sql_auto_is_null"] = 0 + # By default, MySQL 'where id is null' selects the last inserted id; Turn this off. + variables["sql_auto_is_null"] = 0 - # Increase timeout so the server doesn't disconnect us. - wait_timeout = @config[:wait_timeout] - wait_timeout = 2147483 unless wait_timeout.is_a?(Integer) - variables["wait_timeout"] = self.class.type_cast_config_to_integer(wait_timeout) + # Increase timeout so the server doesn't disconnect us. + wait_timeout = @config[:wait_timeout] + wait_timeout = 2147483 unless wait_timeout.is_a?(Integer) + variables["wait_timeout"] = self.class.type_cast_config_to_integer(wait_timeout) - defaults = [":default", :default].to_set + defaults = [":default", :default].to_set - # Make MySQL reject illegal values rather than truncating or blanking them, see - # http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables - # If the user has provided another value for sql_mode, don't replace it. - if sql_mode = variables.delete("sql_mode") - sql_mode = quote(sql_mode) - elsif !defaults.include?(strict_mode?) - if strict_mode? - sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')" - else - sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')" - sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')" - sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')" + # Make MySQL reject illegal values rather than truncating or blanking them, see + # http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables + # If the user has provided another value for sql_mode, don't replace it. + if sql_mode = variables.delete("sql_mode") + sql_mode = quote(sql_mode) + elsif !defaults.include?(strict_mode?) + if strict_mode? + sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')" + else + sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')" + sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')" + sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')" + end + sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')" end - sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')" - end - sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode - - # NAMES does not have an equals sign, see - # http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430 - # (trailing comma because variable_assignments will always have content) - if @config[:encoding] - encoding = "NAMES #{@config[:encoding]}" - encoding << " COLLATE #{@config[:collation]}" if @config[:collation] - encoding << ", " - end - - # Gather up all of the SET variables... - variable_assignments = variables.map do |k, v| - if defaults.include?(v) - "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default - elsif !v.nil? - "@@SESSION.#{k} = #{quote(v)}" + sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode + + # NAMES does not have an equals sign, see + # http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430 + # (trailing comma because variable_assignments will always have content) + if @config[:encoding] + encoding = "NAMES #{@config[:encoding]}" + encoding << " COLLATE #{@config[:collation]}" if @config[:collation] + encoding << ", " end - # or else nil; compact to clear nils out - end.compact.join(", ") - # ...and send them all in one query - @connection.query "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}" - end + # Gather up all of the SET variables... + variable_assignments = variables.map do |k, v| + if defaults.include?(v) + "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default + elsif !v.nil? + "@@SESSION.#{k} = #{quote(v)}" + end + # or else nil; compact to clear nils out + end.compact.join(", ") - def column_definitions(table_name) # :nodoc: - execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result| - each_hash(result) + # ...and send them all in one query + @connection.query "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}" end - end - def extract_foreign_key_action(specifier) # :nodoc: - case specifier - when "CASCADE"; :cascade - when "SET NULL"; :nullify + def column_definitions(table_name) # :nodoc: + execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result| + each_hash(result) + end end - end - - def create_table_info(table_name) # :nodoc: - select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"] - end - def create_table_definition(*args) # :nodoc: - MySQL::TableDefinition.new(*args) - end + def extract_foreign_key_action(specifier) # :nodoc: + case specifier + when "CASCADE"; :cascade + when "SET NULL"; :nullify + end + end - def extract_schema_qualified_name(string) # :nodoc: - schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/) - schema, name = @config[:database], schema unless name - [schema, name] - end + def create_table_info(table_name) # :nodoc: + select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"] + end - def integer_to_sql(limit) # :nodoc: - case limit - when 1; "tinyint" - when 2; "smallint" - when 3; "mediumint" - when nil, 4; "int" - when 5..8; "bigint" - else raise(ActiveRecordError, "No integer type has byte size #{limit}") + def create_table_definition(*args) # :nodoc: + MySQL::TableDefinition.new(*args) end - end - def text_to_sql(limit) # :nodoc: - case limit - when 0..0xff; "tinytext" - when nil, 0x100..0xffff; "text" - when 0x10000..0xffffff; "mediumtext" - when 0x1000000..0xffffffff; "longtext" - else raise(ActiveRecordError, "No text type has byte length #{limit}") + def extract_schema_qualified_name(string) # :nodoc: + schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/) + schema, name = @config[:database], schema unless name + [schema, name] end - end - def binary_to_sql(limit) # :nodoc: - case limit - when 0..0xff; "tinyblob" - when nil, 0x100..0xffff; "blob" - when 0x10000..0xffffff; "mediumblob" - when 0x1000000..0xffffffff; "longblob" - else raise(ActiveRecordError, "No binary type has byte length #{limit}") + def integer_to_sql(limit) # :nodoc: + case limit + when 1; "tinyint" + when 2; "smallint" + when 3; "mediumint" + when nil, 4; "int" + when 5..8; "bigint" + else raise(ActiveRecordError, "No integer type has byte size #{limit}") + end end - end - class MysqlJson < Type::Internal::AbstractJson # :nodoc: - def changed_in_place?(raw_old_value, new_value) - # Normalization is required because MySQL JSON data format includes - # the space between the elements. - super(serialize(deserialize(raw_old_value)), new_value) + def text_to_sql(limit) # :nodoc: + case limit + when 0..0xff; "tinytext" + when nil, 0x100..0xffff; "text" + when 0x10000..0xffffff; "mediumtext" + when 0x1000000..0xffffffff; "longtext" + else raise(ActiveRecordError, "No text type has byte length #{limit}") + end end - end - class MysqlString < Type::String # :nodoc: - def serialize(value) - case value - when true then MySQL::Quoting::QUOTED_TRUE - when false then MySQL::Quoting::QUOTED_FALSE - else super + def binary_to_sql(limit) # :nodoc: + case limit + when 0..0xff; "tinyblob" + when nil, 0x100..0xffff; "blob" + when 0x10000..0xffffff; "mediumblob" + when 0x1000000..0xffffffff; "longblob" + else raise(ActiveRecordError, "No binary type has byte length #{limit}") end end - private + class MysqlJson < Type::Internal::AbstractJson # :nodoc: + def changed_in_place?(raw_old_value, new_value) + # Normalization is required because MySQL JSON data format includes + # the space between the elements. + super(serialize(deserialize(raw_old_value)), new_value) + end + end - def cast_value(value) - case value - when true then MySQL::Quoting::QUOTED_TRUE - when false then MySQL::Quoting::QUOTED_FALSE - else super + class MysqlString < Type::String # :nodoc: + def serialize(value) + case value + when true then MySQL::Quoting::QUOTED_TRUE + when false then MySQL::Quoting::QUOTED_FALSE + else super + end end + + private + + def cast_value(value) + case value + when true then MySQL::Quoting::QUOTED_TRUE + when false then MySQL::Quoting::QUOTED_FALSE + else super + end + end end - end - ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2) - ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2) - ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2) + ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2) + ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2) + ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2) end end end diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 28f0c8686a..1808173592 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -52,9 +52,9 @@ module ActiveRecord protected - def attributes_for_hash - [self.class, name, default, sql_type_metadata, null, table_name, default_function, collation] - end + def attributes_for_hash + [self.class, name, default, sql_type_metadata, null, table_name, default_function, collation] + end end class NullColumn < Column diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 996075e08a..c0c7ce46b2 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -56,13 +56,13 @@ module ActiveRecord private - def uri - @uri - end + def uri + @uri + end - def uri_parser - @uri_parser ||= URI::Parser.new - end + def uri_parser + @uri_parser ||= URI::Parser.new + end # Converts the query parameters of the URI into a hash. # @@ -73,39 +73,39 @@ module ActiveRecord # # "localhost" # # => {} - def query_hash - Hash[(@query || "").split("&").map { |pair| pair.split("=") }] - end + def query_hash + Hash[(@query || "").split("&").map { |pair| pair.split("=") }] + end - def raw_config - if uri.opaque - query_hash.merge( "adapter" => @adapter, - "database" => uri.opaque) - else - query_hash.merge( "adapter" => @adapter, - "username" => uri.user, - "password" => uri.password, - "port" => uri.port, - "database" => database_from_path, - "host" => uri.hostname) + def raw_config + if uri.opaque + query_hash.merge( "adapter" => @adapter, + "database" => uri.opaque) + else + query_hash.merge( "adapter" => @adapter, + "username" => uri.user, + "password" => uri.password, + "port" => uri.port, + "database" => database_from_path, + "host" => uri.hostname) + end end - end # Returns name of the database. - def database_from_path - if @adapter == "sqlite3" - # 'sqlite3:/foo' is absolute, because that makes sense. The - # corresponding relative version, 'sqlite3:foo', is handled - # elsewhere, as an "opaque". + def database_from_path + if @adapter == "sqlite3" + # 'sqlite3:/foo' is absolute, because that makes sense. The + # corresponding relative version, 'sqlite3:foo', is handled + # elsewhere, as an "opaque". - uri.path - else - # Only SQLite uses a filename as the "database" name; for - # anything else, a leading slash would be silly. + uri.path + else + # Only SQLite uses a filename as the "database" name; for + # anything else, a leading slash would be silly. - uri.path.sub(%r{^/}, "") + uri.path.sub(%r{^/}, "") + end end - end end ## @@ -211,16 +211,16 @@ module ActiveRecord # Resolver.new({}).resolve_connection("postgresql://localhost/foo") # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } # - def resolve_connection(spec) - case spec - when Symbol - resolve_symbol_connection spec - when String - resolve_url_connection spec - when Hash - resolve_hash_connection spec + def resolve_connection(spec) + case spec + when Symbol + resolve_symbol_connection spec + when String + resolve_url_connection spec + when Hash + resolve_hash_connection spec + end end - end # Takes the environment such as +:production+ or +:development+. # This requires that the @configurations was initialized with a key that @@ -229,34 +229,34 @@ module ActiveRecord # Resolver.new("production" => {}).resolve_symbol_connection(:production) # # => {} # - def resolve_symbol_connection(spec) - if config = configurations[spec.to_s] - resolve_connection(config).merge("name" => spec.to_s) - else - raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available: #{configurations.keys.inspect}") + def resolve_symbol_connection(spec) + if config = configurations[spec.to_s] + resolve_connection(config).merge("name" => spec.to_s) + else + raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available: #{configurations.keys.inspect}") + end end - end # Accepts a hash. Expands the "url" key that contains a # URL database connection to a full connection # hash and merges with the rest of the hash. # Connection details inside of the "url" key win any merge conflicts - def resolve_hash_connection(spec) - if spec["url"] && spec["url"] !~ /^jdbc:/ - connection_hash = resolve_url_connection(spec.delete("url")) - spec.merge!(connection_hash) + def resolve_hash_connection(spec) + if spec["url"] && spec["url"] !~ /^jdbc:/ + connection_hash = resolve_url_connection(spec.delete("url")) + spec.merge!(connection_hash) + end + spec end - spec - end # Takes a connection URL. # # Resolver.new({}).resolve_url_connection("postgresql://localhost/foo") # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } # - def resolve_url_connection(url) - ConnectionUrlResolver.new(url).to_hash - end + def resolve_url_connection(url) + ConnectionUrlResolver.new(url).to_hash + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/column.rb b/activerecord/lib/active_record/connection_adapters/mysql/column.rb index cb068a61c7..5452e44c84 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/column.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/column.rb @@ -32,11 +32,11 @@ module ActiveRecord private - def extract_default - if blob_or_text_column? - @default = null || strict ? nil : "" + def extract_default + if blob_or_text_column? + @default = null || strict ? nil : "" + end end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb index cd606d11a7..c8238eb266 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb @@ -56,56 +56,56 @@ module ActiveRecord protected - def last_inserted_id(result) - @connection.last_id - end + def last_inserted_id(result) + @connection.last_id + end private - def select_result(sql, name = nil, binds = []) - if without_prepared_statement?(binds) - execute_and_free(sql, name) { |result| yield result } - else - exec_stmt_and_free(sql, name, binds, cache_stmt: true) { |_, result| yield result } - end - end - - def exec_stmt_and_free(sql, name, binds, cache_stmt: false) - if @connection - # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been - # made since we established the connection - @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone + def select_result(sql, name = nil, binds = []) + if without_prepared_statement?(binds) + execute_and_free(sql, name) { |result| yield result } + else + exec_stmt_and_free(sql, name, binds, cache_stmt: true) { |_, result| yield result } + end end - type_casted_binds = type_casted_binds(binds) - - log(sql, name, binds, type_casted_binds) do - if cache_stmt - cache = @statements[sql] ||= { - stmt: @connection.prepare(sql) - } - stmt = cache[:stmt] - else - stmt = @connection.prepare(sql) + def exec_stmt_and_free(sql, name, binds, cache_stmt: false) + if @connection + # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been + # made since we established the connection + @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone end - begin - result = stmt.execute(*type_casted_binds) - rescue Mysql2::Error => e + type_casted_binds = type_casted_binds(binds) + + log(sql, name, binds, type_casted_binds) do if cache_stmt - @statements.delete(sql) + cache = @statements[sql] ||= { + stmt: @connection.prepare(sql) + } + stmt = cache[:stmt] else - stmt.close + stmt = @connection.prepare(sql) + end + + begin + result = stmt.execute(*type_casted_binds) + rescue Mysql2::Error => e + if cache_stmt + @statements.delete(sql) + else + stmt.close + end + raise e end - raise e - end - ret = yield stmt, result - result.free if result - stmt.close unless cache_stmt - ret + ret = yield stmt, result + result.free if result + stmt.close unless cache_stmt + ret + end end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb b/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb index 5a6b33cb04..0b7dea232f 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb @@ -36,34 +36,34 @@ module ActiveRecord private - def compute_column_widths(result) - [].tap do |widths| - result.columns.each_with_index do |column, i| - cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? "NULL" : r[i].to_s} - widths << cells_in_column.map(&:length).max + def compute_column_widths(result) + [].tap do |widths| + result.columns.each_with_index do |column, i| + cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? "NULL" : r[i].to_s} + widths << cells_in_column.map(&:length).max + end end end - end - def build_separator(widths) - padding = 1 - "+" + widths.map {|w| "-" * (w + (padding*2))}.join("+") + "+" - end + def build_separator(widths) + padding = 1 + "+" + widths.map {|w| "-" * (w + (padding*2))}.join("+") + "+" + end - def build_cells(items, widths) - cells = [] - items.each_with_index do |item, i| - item = "NULL" if item.nil? - justifier = item.is_a?(Numeric) ? "rjust" : "ljust" - cells << item.to_s.send(justifier, widths[i]) + def build_cells(items, widths) + cells = [] + items.each_with_index do |item, i| + item = "NULL" if item.nil? + justifier = item.is_a?(Numeric) ? "rjust" : "ljust" + cells << item.to_s.send(justifier, widths[i]) + end + "| " + cells.join(" | ") + " |" end - "| " + cells.join(" | ") + " |" - end - def build_footer(nrows, elapsed) - rows_label = nrows == 1 ? "row" : "rows" - "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed - end + def build_footer(nrows, elapsed) + rows_label = nrows == 1 ? "row" : "rows" + "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb index 381787868b..9d11ad28d4 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb @@ -38,13 +38,13 @@ module ActiveRecord private - def _quote(value) - if value.is_a?(Type::Binary::Data) - "x'#{value.hex}'" - else - super + def _quote(value) + if value.is_a?(Type::Binary::Data) + "x'#{value.hex}'" + else + super + end end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb index fd2dc2aee8..d808b50332 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb @@ -7,60 +7,60 @@ module ActiveRecord private - def visit_DropForeignKey(name) - "DROP FOREIGN KEY #{name}" - end - - def visit_ColumnDefinition(o) - o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.unsigned) - super - end - - def visit_AddColumnDefinition(o) - add_column_position!(super, column_options(o.column)) - end + def visit_DropForeignKey(name) + "DROP FOREIGN KEY #{name}" + end - def visit_ChangeColumnDefinition(o) - change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}" - add_column_position!(change_column_sql, column_options(o.column)) - end + def visit_ColumnDefinition(o) + o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.unsigned) + super + end - def add_table_options!(create_sql, options) - add_sql_comment!(super, options[:comment]) - end + def visit_AddColumnDefinition(o) + add_column_position!(super, column_options(o.column)) + end - def column_options(o) - column_options = super - column_options[:charset] = o.charset - column_options - end + def visit_ChangeColumnDefinition(o) + change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}" + add_column_position!(change_column_sql, column_options(o.column)) + end - def add_column_options!(sql, options) - if charset = options[:charset] - sql << " CHARACTER SET #{charset}" + def add_table_options!(create_sql, options) + add_sql_comment!(super, options[:comment]) end - if collation = options[:collation] - sql << " COLLATE #{collation}" + def column_options(o) + column_options = super + column_options[:charset] = o.charset + column_options end - add_sql_comment!(super, options[:comment]) - end + def add_column_options!(sql, options) + if charset = options[:charset] + sql << " CHARACTER SET #{charset}" + end + + if collation = options[:collation] + sql << " COLLATE #{collation}" + end - def add_column_position!(sql, options) - if options[:first] - sql << " FIRST" - elsif options[:after] - sql << " AFTER #{quote_column_name(options[:after])}" + add_sql_comment!(super, options[:comment]) end - sql - end + def add_column_position!(sql, options) + if options[:first] + sql << " FIRST" + elsif options[:after] + sql << " AFTER #{quote_column_name(options[:after])}" + end - def index_in_create(table_name, column_name, options) - index_name, index_type, index_columns, _, _, index_using, comment = @conn.add_index_options(table_name, column_name, options) - add_sql_comment!("#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})", comment) - end + sql + end + + def index_in_create(table_name, column_name, options) + index_name, index_type, index_columns, _, _, index_using, comment = @conn.add_index_options(table_name, column_name, options) + add_sql_comment!("#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})", comment) + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb index 157e75dbf7..ce773ed75b 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb @@ -80,9 +80,9 @@ module ActiveRecord private - def create_column_definition(name, type) - MySQL::ColumnDefinition.new(name, type) - end + def create_column_definition(name, type) + MySQL::ColumnDefinition.new(name, type) + end end class Table < ActiveRecord::ConnectionAdapters::Table diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb index 3e644f386f..39221eeb0c 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb @@ -25,29 +25,29 @@ module ActiveRecord private - def default_primary_key?(column) - super && column.auto_increment? - end + def default_primary_key?(column) + super && column.auto_increment? + end - def schema_type(column) - if column.sql_type == "tinyblob" - :blob - else - super + def schema_type(column) + if column.sql_type == "tinyblob" + :blob + else + super + end end - end - def schema_precision(column) - super unless /time/ === column.sql_type && column.precision == 0 - end + def schema_precision(column) + super unless /time/ === column.sql_type && column.precision == 0 + end - def schema_collation(column) - if column.collation && table_name = column.table_name - @table_collation_cache ||= {} - @table_collation_cache[table_name] ||= select_one("SHOW TABLE STATUS LIKE '#{table_name}'")["Collation"] - column.collation.inspect if column.collation != @table_collation_cache[table_name] + def schema_collation(column) + if column.collation && table_name = column.table_name + @table_collation_cache ||= {} + @table_collation_cache[table_name] ||= select_one("SHOW TABLE STATUS LIKE '#{table_name}'")["Collation"] + column.collation.inspect if column.collation != @table_collation_cache[table_name] + end end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb index e1e3f7b472..1be5cb4740 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb @@ -23,9 +23,9 @@ module ActiveRecord protected - def attributes_for_hash - [self.class, @type_metadata, extra, strict] - end + def attributes_for_hash + [self.class, @type_metadata, extra, strict] + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 154f0b2d3a..0130b4ef62 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -113,19 +113,19 @@ module ActiveRecord private - def connect - @connection = Mysql2::Client.new(@config) - configure_connection - end + def connect + @connection = Mysql2::Client.new(@config) + configure_connection + end - def configure_connection - @connection.query_options.merge!(as: :array) - super - end + def configure_connection + @connection.query_options.merge!(as: :array) + super + end - def full_version - @full_version ||= @connection.server_info[:version] - end + def full_version + @full_version ||= @connection.server_info[:version] + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb index b5a3340c18..87338986b9 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -169,9 +169,9 @@ module ActiveRecord private - def suppress_composite_primary_key(pk) - pk unless pk.is_a?(Array) - end + def suppress_composite_primary_key(pk) + pk unless pk.is_a?(Array) + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb index 9e0e6732c3..1a66afb23a 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb @@ -56,13 +56,13 @@ module ActiveRecord private - def type_cast_array(value, method) - if value.is_a?(::Array) - value.map { |item| type_cast_array(item, method) } - else - @subtype.public_send(method, value) + def type_cast_array(value, method) + if value.is_a?(::Array) + value.map { |item| type_cast_array(item, method) } + else + @subtype.public_send(method, value) + end end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb index ea0fa2517f..a6b5a89ec0 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb @@ -43,7 +43,7 @@ module ActiveRecord protected - attr_reader :value + attr_reader :value end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb index 91d339f32c..950d23d516 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb @@ -9,9 +9,9 @@ module ActiveRecord private - def cast_value(value) - value.to_s - end + def cast_value(value) + value.to_s + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb index 2e55a5f8b8..2d3e6a925d 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb @@ -35,23 +35,23 @@ module ActiveRecord private - HstorePair = begin - quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/ - unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/ - /(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/ - end + HstorePair = begin + quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/ + unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/ + /(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/ + end - def escape_hstore(value) - if value.nil? - "NULL" - else - if value == "" - '""' + def escape_hstore(value) + if value.nil? + "NULL" else - '"%s"' % value.to_s.gsub(/(["\\])/, '\\\\\1') + if value == "" + '""' + else + '"%s"' % value.to_s.gsub(/(["\\])/, '\\\\\1') + end end end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb index 3657f20433..bb4db2564c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb @@ -33,9 +33,9 @@ module ActiveRecord private - def number_for_point(number) - number.to_s.gsub(/\.0$/, "") - end + def number_for_point(number) + number.to_s.gsub(/\.0$/, "") + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb index 3a52f05091..845ff5b6a9 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb @@ -15,7 +15,7 @@ module ActiveRecord case value when ::String return if value.blank? - + if value[0] == "(" && value[-1] == ")" value = value[1...-1] end @@ -38,13 +38,13 @@ module ActiveRecord private - def number_for_point(number) - number.to_s.gsub(/\.0$/, "") - end + def number_for_point(number) + number.to_s.gsub(/\.0$/, "") + end - def build_point(x, y) - ActiveRecord::Point.new(Float(x), Float(y)) - end + def build_point(x, y) + ActiveRecord::Point.new(Float(x), Float(y)) + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb index 58bf5c5640..2c714f4018 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb @@ -55,37 +55,37 @@ module ActiveRecord private - def type_cast_single(value) - infinity?(value) ? value : @subtype.deserialize(value) - end + def type_cast_single(value) + infinity?(value) ? value : @subtype.deserialize(value) + end - def type_cast_single_for_database(value) - infinity?(value) ? "" : @subtype.serialize(value) - end + def type_cast_single_for_database(value) + infinity?(value) ? "" : @subtype.serialize(value) + end - def extract_bounds(value) - from, to = value[1..-2].split(",") - { - from: (value[1] == "," || from == "-infinity") ? infinity(negative: true) : from, - to: (value[-2] == "," || to == "infinity") ? infinity : to, - exclude_start: (value[0] == "("), - exclude_end: (value[-1] == ")") - } - end + def extract_bounds(value) + from, to = value[1..-2].split(",") + { + from: (value[1] == "," || from == "-infinity") ? infinity(negative: true) : from, + to: (value[-2] == "," || to == "infinity") ? infinity : to, + exclude_start: (value[0] == "("), + exclude_end: (value[-1] == ")") + } + end - def infinity(negative: false) - if subtype.respond_to?(:infinity) - subtype.infinity(negative: negative) - elsif negative - -::Float::INFINITY - else - ::Float::INFINITY + def infinity(negative: false) + if subtype.respond_to?(:infinity) + subtype.infinity(negative: negative) + elsif negative + -::Float::INFINITY + else + ::Float::INFINITY + end end - end - def infinity?(value) - value.respond_to?(:infinite?) && value.infinite? - end + def infinity?(value) + value.respond_to?(:infinite?) && value.infinite? + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb index 3a75e8d69d..d9ae1aa7a2 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb @@ -42,66 +42,66 @@ module ActiveRecord end private - def register_mapped_type(row) - alias_type row["oid"], row["typname"] - end + def register_mapped_type(row) + alias_type row["oid"], row["typname"] + end - def register_enum_type(row) - register row["oid"], OID::Enum.new - end + def register_enum_type(row) + register row["oid"], OID::Enum.new + end - def register_array_type(row) - register_with_subtype(row["oid"], row["typelem"].to_i) do |subtype| - OID::Array.new(subtype, row["typdelim"]) + def register_array_type(row) + register_with_subtype(row["oid"], row["typelem"].to_i) do |subtype| + OID::Array.new(subtype, row["typdelim"]) + end end - end - def register_range_type(row) - register_with_subtype(row["oid"], row["rngsubtype"].to_i) do |subtype| - OID::Range.new(subtype, row["typname"].to_sym) + def register_range_type(row) + register_with_subtype(row["oid"], row["rngsubtype"].to_i) do |subtype| + OID::Range.new(subtype, row["typname"].to_sym) + end end - end - def register_domain_type(row) - if base_type = @store.lookup(row["typbasetype"].to_i) - register row["oid"], base_type - else - warn "unknown base type (OID: #{row["typbasetype"]}) for domain #{row["typname"]}." + def register_domain_type(row) + if base_type = @store.lookup(row["typbasetype"].to_i) + register row["oid"], base_type + else + warn "unknown base type (OID: #{row["typbasetype"]}) for domain #{row["typname"]}." + end end - end - def register_composite_type(row) - if subtype = @store.lookup(row["typelem"].to_i) - register row["oid"], OID::Vector.new(row["typdelim"], subtype) + def register_composite_type(row) + if subtype = @store.lookup(row["typelem"].to_i) + register row["oid"], OID::Vector.new(row["typdelim"], subtype) + end end - end - def register(oid, oid_type = nil, &block) - oid = assert_valid_registration(oid, oid_type || block) - if block_given? - @store.register_type(oid, &block) - else - @store.register_type(oid, oid_type) + def register(oid, oid_type = nil, &block) + oid = assert_valid_registration(oid, oid_type || block) + if block_given? + @store.register_type(oid, &block) + else + @store.register_type(oid, oid_type) + end end - end - def alias_type(oid, target) - oid = assert_valid_registration(oid, target) - @store.alias_type(oid, target) - end + def alias_type(oid, target) + oid = assert_valid_registration(oid, target) + @store.alias_type(oid, target) + end - def register_with_subtype(oid, target_oid) - if @store.key?(target_oid) - register(oid) do |_, *args| - yield @store.lookup(target_oid, *args) + def register_with_subtype(oid, target_oid) + if @store.key?(target_oid) + register(oid) do |_, *args| + yield @store.lookup(target_oid, *args) + end end end - end - def assert_valid_registration(oid, oid_type) - raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil? - oid.to_i - end + def assert_valid_registration(oid, oid_type) + raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil? + oid.to_i + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 90e88b1f3c..b5031d890f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -74,42 +74,42 @@ module ActiveRecord private - def _quote(value) - case value - when Type::Binary::Data - "'#{escape_bytea(value.to_s)}'" - when OID::Xml::Data - "xml '#{quote_string(value.to_s)}'" - when OID::Bit::Data - if value.binary? - "B'#{value}'" - elsif value.hex? - "X'#{value}'" - end - when Float - if value.infinite? || value.nan? - "'#{value}'" + def _quote(value) + case value + when Type::Binary::Data + "'#{escape_bytea(value.to_s)}'" + when OID::Xml::Data + "xml '#{quote_string(value.to_s)}'" + when OID::Bit::Data + if value.binary? + "B'#{value}'" + elsif value.hex? + "X'#{value}'" + end + when Float + if value.infinite? || value.nan? + "'#{value}'" + else + super + end else super end - else - super end - end - def _type_cast(value) - case value - when Type::Binary::Data - # Return a bind param hash with format as binary. - # See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc - # for more information - { value: value.to_s, format: 1 } - when OID::Xml::Data, OID::Bit::Data - value.to_s - else - super + def _type_cast(value) + case value + when Type::Binary::Data + # Return a bind param hash with format as binary. + # See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc + # for more information + { value: value.to_s, format: 1 } + when OID::Xml::Data, OID::Bit::Data + value.to_s + else + super + end end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb index 2ed8f29b24..c20baf655c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb @@ -24,23 +24,23 @@ module ActiveRecord private - def default_primary_key?(column) - schema_type(column) == :serial - end + def default_primary_key?(column) + schema_type(column) == :serial + end - def schema_type(column) - return super unless column.serial? + def schema_type(column) + return super unless column.serial? - if column.bigint? - :bigserial - else - :serial + if column.bigint? + :bigserial + else + :serial + end end - end - def schema_expression(column) - super unless column.serial? - end + def schema_expression(column) + super unless column.serial? + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index 55c5fcafa6..56280f15e9 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -6,17 +6,17 @@ module ActiveRecord class SchemaCreation < AbstractAdapter::SchemaCreation private - def visit_ColumnDefinition(o) - o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.array) - super - end + def visit_ColumnDefinition(o) + o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.array) + super + end - def add_column_options!(sql, options) - if options[:collation] - sql << " COLLATE \"#{options[:collation]}\"" + def add_column_options!(sql, options) + if options[:collation] + sql << " COLLATE \"#{options[:collation]}\"" + end + super end - super - end end module SchemaStatements diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb index b2c49989a4..bcef8ac715 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb @@ -27,9 +27,9 @@ module ActiveRecord protected - def attributes_for_hash - [self.class, @type_metadata, oid, fmod] - end + def attributes_for_hash + [self.class, @type_metadata, oid, fmod] + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb b/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb index ccb7e154ee..9e12ae0de8 100644 --- a/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb +++ b/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb @@ -24,9 +24,9 @@ module ActiveRecord protected - def attributes_for_hash - [self.class, sql_type, type, limit, precision, scale] - end + def attributes_for_hash + [self.class, sql_type, type, limit, precision, scale] + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb index fa20175b0e..f01ed67b0f 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb @@ -20,28 +20,28 @@ module ActiveRecord private - def _quote(value) - if value.is_a?(Type::Binary::Data) - "x'#{value.hex}'" - else - super + def _quote(value) + if value.is_a?(Type::Binary::Data) + "x'#{value.hex}'" + else + super + end end - end - def _type_cast(value) - case value - when BigDecimal - value.to_f - when String - if value.encoding == Encoding::ASCII_8BIT - super(value.encode(Encoding::UTF_8)) + def _type_cast(value) + case value + when BigDecimal + value.to_f + when String + if value.encoding == Encoding::ASCII_8BIT + super(value.encode(Encoding::UTF_8)) + else + super + end else super end - else - super end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 4558e1f80f..57699badba 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -70,9 +70,9 @@ module ActiveRecord class StatementPool < ConnectionAdapters::StatementPool private - def dealloc(stmt) - stmt[:stmt].close unless stmt[:stmt].closed? - end + def dealloc(stmt) + stmt[:stmt].close unless stmt[:stmt].closed? + end end def schema_creation # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/statement_pool.rb b/activerecord/lib/active_record/connection_adapters/statement_pool.rb index 9b0ed3e08b..273b1b0b5c 100644 --- a/activerecord/lib/active_record/connection_adapters/statement_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/statement_pool.rb @@ -47,13 +47,13 @@ module ActiveRecord private - def cache - @cache[Process.pid] - end + def cache + @cache[Process.pid] + end - def dealloc(stmt) - raise NotImplementedError - end + def dealloc(stmt) + raise NotImplementedError + end end end end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 26a00e62d1..19a2720913 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -299,26 +299,26 @@ module ActiveRecord private - def cached_find_by_statement(key, &block) # :nodoc: - cache = @find_by_statement_cache[connection.prepared_statements] - cache[key] || cache.synchronize { - cache[key] ||= StatementCache.create(connection, &block) - } - end + def cached_find_by_statement(key, &block) # :nodoc: + cache = @find_by_statement_cache[connection.prepared_statements] + cache[key] || cache.synchronize { + cache[key] ||= StatementCache.create(connection, &block) + } + end - def relation # :nodoc: - relation = Relation.create(self, arel_table, predicate_builder) + def relation # :nodoc: + relation = Relation.create(self, arel_table, predicate_builder) - if finder_needs_type_condition? && !ignore_default_scope? - relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name) - else - relation + if finder_needs_type_condition? && !ignore_default_scope? + relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name) + else + relation + end end - end - def table_metadata # :nodoc: - TableMetadata.new(self, arel_table) - end + def table_metadata # :nodoc: + TableMetadata.new(self, arel_table) + end end # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with @@ -498,11 +498,11 @@ module ActiveRecord # We check defined?(@attributes) not to issue warnings if the object is # allocated but not initialized. inspection = if defined?(@attributes) && @attributes - self.class.column_names.collect { |name| - if has_attribute?(name) - "#{name}: #{attribute_for_inspect(name)}" - end - }.compact.join(", ") + self.class.column_names.collect { |name| + if has_attribute?(name) + "#{name}: #{attribute_for_inspect(name)}" + end + }.compact.join(", ") else "not initialized" end @@ -548,32 +548,32 @@ module ActiveRecord # So we can avoid the method_missing hit by explicitly defining #to_ary as nil here. # # See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html - def to_ary # :nodoc: - nil - end + def to_ary # :nodoc: + nil + end - def init_internals - @readonly = false - @destroyed = false - @marked_for_destruction = false - @destroyed_by_association = nil - @new_record = true - @txn = nil - @_start_transaction_state = {} - @transaction_state = nil - end + def init_internals + @readonly = false + @destroyed = false + @marked_for_destruction = false + @destroyed_by_association = nil + @new_record = true + @txn = nil + @_start_transaction_state = {} + @transaction_state = nil + end - def initialize_internals_callback - end + def initialize_internals_callback + end - def thaw - if frozen? - @attributes = @attributes.dup + def thaw + if frozen? + @attributes = @attributes.dup + end end - end - def custom_inspect_method_defined? - self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner - end + def custom_inspect_method_defined? + self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner + end end end diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb index b41546064b..fd2fa7410a 100644 --- a/activerecord/lib/active_record/dynamic_matchers.rb +++ b/activerecord/lib/active_record/dynamic_matchers.rb @@ -13,111 +13,111 @@ module ActiveRecord private - def method_missing(name, *arguments, &block) - match = Method.match(self, name) + def method_missing(name, *arguments, &block) + match = Method.match(self, name) - if match && match.valid? - match.define - send(name, *arguments, &block) - else - super + if match && match.valid? + match.define + send(name, *arguments, &block) + else + super + end end - end - class Method - @matchers = [] + class Method + @matchers = [] - class << self - attr_reader :matchers + class << self + attr_reader :matchers - def match(model, name) - klass = matchers.find { |k| k.pattern.match?(name) } - klass.new(model, name) if klass - end + def match(model, name) + klass = matchers.find { |k| k.pattern.match?(name) } + klass.new(model, name) if klass + end - def pattern - @pattern ||= /\A#{prefix}_([_a-zA-Z]\w*)#{suffix}\Z/ - end + def pattern + @pattern ||= /\A#{prefix}_([_a-zA-Z]\w*)#{suffix}\Z/ + end - def prefix - raise NotImplementedError - end + def prefix + raise NotImplementedError + end - def suffix - "" + def suffix + "" + end end - end - attr_reader :model, :name, :attribute_names + attr_reader :model, :name, :attribute_names - def initialize(model, name) - @model = model - @name = name.to_s - @attribute_names = @name.match(self.class.pattern)[1].split("_and_") - @attribute_names.map! { |n| @model.attribute_aliases[n] || n } - end + def initialize(model, name) + @model = model + @name = name.to_s + @attribute_names = @name.match(self.class.pattern)[1].split("_and_") + @attribute_names.map! { |n| @model.attribute_aliases[n] || n } + end - def valid? - attribute_names.all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) } - end + def valid? + attribute_names.all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) } + end - def define - model.class_eval <<-CODE, __FILE__, __LINE__ + 1 + def define + model.class_eval <<-CODE, __FILE__, __LINE__ + 1 def self.#{name}(#{signature}) #{body} end CODE - end + end - private + private - def body - "#{finder}(#{attributes_hash})" - end + def body + "#{finder}(#{attributes_hash})" + end - # The parameters in the signature may have reserved Ruby words, in order - # to prevent errors, we start each param name with `_`. - def signature - attribute_names.map { |name| "_#{name}" }.join(", ") - end + # The parameters in the signature may have reserved Ruby words, in order + # to prevent errors, we start each param name with `_`. + def signature + attribute_names.map { |name| "_#{name}" }.join(", ") + end - # Given that the parameters starts with `_`, the finder needs to use the - # same parameter name. - def attributes_hash - "{" + attribute_names.map { |name| ":#{name} => _#{name}" }.join(",") + "}" - end + # Given that the parameters starts with `_`, the finder needs to use the + # same parameter name. + def attributes_hash + "{" + attribute_names.map { |name| ":#{name} => _#{name}" }.join(",") + "}" + end - def finder - raise NotImplementedError + def finder + raise NotImplementedError + end end - end - class FindBy < Method - Method.matchers << self + class FindBy < Method + Method.matchers << self - def self.prefix - "find_by" - end + def self.prefix + "find_by" + end - def finder - "find_by" + def finder + "find_by" + end end - end - class FindByBang < Method - Method.matchers << self + class FindByBang < Method + Method.matchers << self - def self.prefix - "find_by" - end + def self.prefix + "find_by" + end - def self.suffix - "!" - end + def self.suffix + "!" + end - def finder - "find_by!" + def finder + "find_by!" + end end - end end end diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index dcf1004879..73f122cfd7 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -142,7 +142,7 @@ module ActiveRecord protected - attr_reader :name, :mapping, :subtype + attr_reader :name, :mapping, :subtype end def enum(definitions) diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index babff9c284..0dc64c8853 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -426,9 +426,9 @@ module ActiveRecord end def self.default_fixture_table_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc: - "#{ config.table_name_prefix }"\ - "#{ fixture_set_name.tr('/', '_') }"\ - "#{ config.table_name_suffix }".to_sym + "#{ config.table_name_prefix }"\ + "#{ fixture_set_name.tr('/', '_') }"\ + "#{ config.table_name_suffix }".to_sym end def self.reset_cache @@ -494,18 +494,18 @@ module ActiveRecord private - def insert_class(class_names, name, klass) - # We only want to deal with AR objects. - if klass && klass < ActiveRecord::Base - class_names[name] = klass - else - class_names[name] = nil + def insert_class(class_names, name, klass) + # We only want to deal with AR objects. + if klass && klass < ActiveRecord::Base + class_names[name] = klass + else + class_names[name] = nil + end end - end - def default_fixture_model(fs_name, config) - ActiveRecord::FixtureSet.default_fixture_model_name(fs_name, config) - end + def default_fixture_model(fs_name, config) + ActiveRecord::FixtureSet.default_fixture_model_name(fs_name, config) + end end def self.create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base) diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index 5863f622b5..4adcd7e65c 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -134,83 +134,83 @@ module ActiveRecord # Returns the class type of the record using the current module as a prefix. So descendants of # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass. - def compute_type(type_name) - if type_name.match(/^::/) - # If the type is prefixed with a scope operator then we assume that - # the type_name is an absolute reference. - ActiveSupport::Dependencies.constantize(type_name) - else - # Build a list of candidates to search for - candidates = [] - name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" } - candidates << type_name - - candidates.each do |candidate| - constant = ActiveSupport::Dependencies.safe_constantize(candidate) - return constant if candidate == constant.to_s - end + def compute_type(type_name) + if type_name.match(/^::/) + # If the type is prefixed with a scope operator then we assume that + # the type_name is an absolute reference. + ActiveSupport::Dependencies.constantize(type_name) + else + # Build a list of candidates to search for + candidates = [] + name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" } + candidates << type_name - raise NameError.new("uninitialized constant #{candidates.first}", candidates.first) + candidates.each do |candidate| + constant = ActiveSupport::Dependencies.safe_constantize(candidate) + return constant if candidate == constant.to_s + end + + raise NameError.new("uninitialized constant #{candidates.first}", candidates.first) + end end - end private # Called by +instantiate+ to decide which class to use for a new # record instance. For single-table inheritance, we check the record # for a +type+ column and return the corresponding class. - def discriminate_class_for_record(record) - if using_single_table_inheritance?(record) - find_sti_class(record[inheritance_column]) - else - super + def discriminate_class_for_record(record) + if using_single_table_inheritance?(record) + find_sti_class(record[inheritance_column]) + else + super + end end - end - def using_single_table_inheritance?(record) - record[inheritance_column].present? && has_attribute?(inheritance_column) - end + def using_single_table_inheritance?(record) + record[inheritance_column].present? && has_attribute?(inheritance_column) + end - def find_sti_class(type_name) - type_name = base_class.type_for_attribute(inheritance_column).cast(type_name) - subclass = begin - if store_full_sti_class - ActiveSupport::Dependencies.constantize(type_name) - else - compute_type(type_name) + def find_sti_class(type_name) + type_name = base_class.type_for_attribute(inheritance_column).cast(type_name) + subclass = begin + if store_full_sti_class + ActiveSupport::Dependencies.constantize(type_name) + else + compute_type(type_name) + end + rescue NameError + raise SubclassNotFound, + "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \ + "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " \ + "Please rename this column if you didn't intend it to be used for storing the inheritance class " \ + "or overwrite #{name}.inheritance_column to use another column for that information." end - rescue NameError - raise SubclassNotFound, - "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \ - "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " \ - "Please rename this column if you didn't intend it to be used for storing the inheritance class " \ - "or overwrite #{name}.inheritance_column to use another column for that information." - end - unless subclass == self || descendants.include?(subclass) - raise SubclassNotFound, "Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}" + unless subclass == self || descendants.include?(subclass) + raise SubclassNotFound, "Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}" + end + subclass end - subclass - end - def type_condition(table = arel_table) - sti_column = arel_attribute(inheritance_column, table) - sti_names = ([self] + descendants).map(&:sti_name) + def type_condition(table = arel_table) + sti_column = arel_attribute(inheritance_column, table) + sti_names = ([self] + descendants).map(&:sti_name) - sti_column.in(sti_names) - end + sti_column.in(sti_names) + end # Detect the subclass from the inheritance column of attrs. If the inheritance column value # is not self or a valid subclass, raises ActiveRecord::SubclassNotFound - def subclass_from_attributes(attrs) - attrs = attrs.to_h if attrs.respond_to?(:permitted?) - if attrs.is_a?(Hash) - subclass_name = attrs.with_indifferent_access[inheritance_column] - - if subclass_name.present? - find_sti_class(subclass_name) + def subclass_from_attributes(attrs) + attrs = attrs.to_h if attrs.respond_to?(:permitted?) + if attrs.is_a?(Hash) + subclass_name = attrs.with_indifferent_access[inheritance_column] + + if subclass_name.present? + find_sti_class(subclass_name) + end end end - end end def initialize_dup(other) @@ -220,21 +220,21 @@ module ActiveRecord private - def initialize_internals_callback - super - ensure_proper_type - end + def initialize_internals_callback + super + ensure_proper_type + end # Sets the attribute used for single table inheritance to this class name if this is not the # ActiveRecord::Base descendant. # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to # do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself. # No such attribute would be set for objects of the Message class in that example. - def ensure_proper_type - klass = self.class - if klass.finder_needs_type_condition? - write_attribute(klass.inheritance_column, klass.sti_name) + def ensure_proper_type + klass = self.class + if klass.finder_needs_type_condition? + write_attribute(klass.inheritance_column, klass.sti_name) + end end - end end end diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 5e519056a3..21a6a2c55f 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -132,56 +132,56 @@ module ActiveRecord relation end - module ClassMethods - DEFAULT_LOCKING_COLUMN = "lock_version" - - # Returns true if the +lock_optimistically+ flag is set to true - # (which it is, by default) and the table includes the - # +locking_column+ column (defaults to +lock_version+). - def locking_enabled? - lock_optimistically && columns_hash[locking_column] - end + module ClassMethods + DEFAULT_LOCKING_COLUMN = "lock_version" + + # Returns true if the +lock_optimistically+ flag is set to true + # (which it is, by default) and the table includes the + # +locking_column+ column (defaults to +lock_version+). + def locking_enabled? + lock_optimistically && columns_hash[locking_column] + end - # Set the column to use for optimistic locking. Defaults to +lock_version+. - def locking_column=(value) - reload_schema_from_cache - @locking_column = value.to_s - end + # Set the column to use for optimistic locking. Defaults to +lock_version+. + def locking_column=(value) + reload_schema_from_cache + @locking_column = value.to_s + end - # The version column used for optimistic locking. Defaults to +lock_version+. - def locking_column - @locking_column = DEFAULT_LOCKING_COLUMN unless defined?(@locking_column) - @locking_column - end + # The version column used for optimistic locking. Defaults to +lock_version+. + def locking_column + @locking_column = DEFAULT_LOCKING_COLUMN unless defined?(@locking_column) + @locking_column + end - # Reset the column used for optimistic locking back to the +lock_version+ default. - def reset_locking_column - self.locking_column = DEFAULT_LOCKING_COLUMN - end + # Reset the column used for optimistic locking back to the +lock_version+ default. + def reset_locking_column + self.locking_column = DEFAULT_LOCKING_COLUMN + end - # Make sure the lock version column gets updated when counters are - # updated. - def update_counters(id, counters) - counters = counters.merge(locking_column => 1) if locking_enabled? - super - end + # Make sure the lock version column gets updated when counters are + # updated. + def update_counters(id, counters) + counters = counters.merge(locking_column => 1) if locking_enabled? + super + end - private - - # We need to apply this decorator here, rather than on module inclusion. The closure - # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the - # sub class being decorated. As such, changes to `lock_optimistically`, or - # `locking_column` would not be picked up. - def inherited(subclass) - subclass.class_eval do - is_lock_column = ->(name, _) { lock_optimistically && name == locking_column } - decorate_matching_attribute_types(is_lock_column, :_optimistic_locking) do |type| - LockingType.new(type) + private + + # We need to apply this decorator here, rather than on module inclusion. The closure + # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the + # sub class being decorated. As such, changes to `lock_optimistically`, or + # `locking_column` would not be picked up. + def inherited(subclass) + subclass.class_eval do + is_lock_column = ->(name, _) { lock_optimistically && name == locking_column } + decorate_matching_attribute_types(is_lock_column, :_optimistic_locking) do |type| + LockingType.new(type) + end + end + super end - end - super end - end end diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb index c4998ba686..bb9f898cf0 100644 --- a/activerecord/lib/active_record/log_subscriber.rb +++ b/activerecord/lib/active_record/log_subscriber.rb @@ -57,38 +57,38 @@ module ActiveRecord private - def colorize_payload_name(name, payload_name) - if payload_name.blank? || payload_name == "SQL" # SQL vs Model Load/Exists - color(name, MAGENTA, true) - else - color(name, CYAN, true) + def colorize_payload_name(name, payload_name) + if payload_name.blank? || payload_name == "SQL" # SQL vs Model Load/Exists + color(name, MAGENTA, true) + else + color(name, CYAN, true) + end end - end - def sql_color(sql) - case sql - when /\A\s*rollback/mi - RED - when /select .*for update/mi, /\A\s*lock/mi - WHITE - when /\A\s*select/i - BLUE - when /\A\s*insert/i - GREEN - when /\A\s*update/i - YELLOW - when /\A\s*delete/i - RED - when /transaction\s*\Z/i - CYAN - else - MAGENTA + def sql_color(sql) + case sql + when /\A\s*rollback/mi + RED + when /select .*for update/mi, /\A\s*lock/mi + WHITE + when /\A\s*select/i + BLUE + when /\A\s*insert/i + GREEN + when /\A\s*update/i + YELLOW + when /\A\s*delete/i + RED + when /transaction\s*\Z/i + CYAN + else + MAGENTA + end end - end - def logger - ActiveRecord::Base.logger - end + def logger + ActiveRecord::Base.logger + end end end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 0066d30ca6..a8dbda118e 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -555,9 +555,9 @@ module ActiveRecord private - def connection - ActiveRecord::Base.connection - end + def connection + ActiveRecord::Base.connection + end end class << self @@ -922,13 +922,13 @@ module ActiveRecord end private - def execute_block - if connection.respond_to? :execute_block - super # use normal delegation to record the block - else - yield + def execute_block + if connection.respond_to? :execute_block + super # use normal delegation to record the block + else + yield + end end - end end # MigrationProxy is used to defer loading of the actual migration classes @@ -1166,145 +1166,145 @@ module ActiveRecord private # Used for running a specific migration. - def run_without_lock - migration = migrations.detect { |m| m.version == @target_version } - raise UnknownMigrationVersionError.new(@target_version) if migration.nil? - execute_migration_in_transaction(migration, @direction) + def run_without_lock + migration = migrations.detect { |m| m.version == @target_version } + raise UnknownMigrationVersionError.new(@target_version) if migration.nil? + execute_migration_in_transaction(migration, @direction) - record_environment - end + record_environment + end # Used for running multiple migrations up to or down to a certain value. - def migrate_without_lock - if invalid_target? - raise UnknownMigrationVersionError.new(@target_version) - end + def migrate_without_lock + if invalid_target? + raise UnknownMigrationVersionError.new(@target_version) + end - runnable.each do |migration| - execute_migration_in_transaction(migration, @direction) - end + runnable.each do |migration| + execute_migration_in_transaction(migration, @direction) + end - record_environment - end + record_environment + end # Stores the current environment in the database. - def record_environment - return if down? - ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment - end + def record_environment + return if down? + ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment + end - def ran?(migration) - migrated.include?(migration.version.to_i) - end + def ran?(migration) + migrated.include?(migration.version.to_i) + end # Return true if a valid version is not provided. - def invalid_target? - !target && @target_version && @target_version > 0 - end + def invalid_target? + !target && @target_version && @target_version > 0 + end - def execute_migration_in_transaction(migration, direction) - return if down? && !migrated.include?(migration.version.to_i) - return if up? && migrated.include?(migration.version.to_i) + def execute_migration_in_transaction(migration, direction) + return if down? && !migrated.include?(migration.version.to_i) + return if up? && migrated.include?(migration.version.to_i) - Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger + Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger - ddl_transaction(migration) do - migration.migrate(direction) - record_version_state_after_migrating(migration.version) + ddl_transaction(migration) do + migration.migrate(direction) + record_version_state_after_migrating(migration.version) + end + rescue => e + msg = "An error has occurred, " + msg << "this and " if use_transaction?(migration) + msg << "all later migrations canceled:\n\n#{e}" + raise StandardError, msg, e.backtrace end - rescue => e - msg = "An error has occurred, " - msg << "this and " if use_transaction?(migration) - msg << "all later migrations canceled:\n\n#{e}" - raise StandardError, msg, e.backtrace - end - def target - migrations.detect { |m| m.version == @target_version } - end + def target + migrations.detect { |m| m.version == @target_version } + end - def finish - migrations.index(target) || migrations.size - 1 - end + def finish + migrations.index(target) || migrations.size - 1 + end - def start - up? ? 0 : (migrations.index(current) || 0) - end + def start + up? ? 0 : (migrations.index(current) || 0) + end - def validate(migrations) - name ,= migrations.group_by(&:name).find { |_,v| v.length > 1 } - raise DuplicateMigrationNameError.new(name) if name + def validate(migrations) + name ,= migrations.group_by(&:name).find { |_,v| v.length > 1 } + raise DuplicateMigrationNameError.new(name) if name - version ,= migrations.group_by(&:version).find { |_,v| v.length > 1 } - raise DuplicateMigrationVersionError.new(version) if version - end + version ,= migrations.group_by(&:version).find { |_,v| v.length > 1 } + raise DuplicateMigrationVersionError.new(version) if version + end - def record_version_state_after_migrating(version) - if down? - migrated.delete(version) - ActiveRecord::SchemaMigration.where(version: version.to_s).delete_all - else - migrated << version - ActiveRecord::SchemaMigration.create!(version: version.to_s) + def record_version_state_after_migrating(version) + if down? + migrated.delete(version) + ActiveRecord::SchemaMigration.where(version: version.to_s).delete_all + else + migrated << version + ActiveRecord::SchemaMigration.create!(version: version.to_s) + end end - end - def self.last_stored_environment - return nil if current_version == 0 - raise NoEnvironmentInSchemaError unless ActiveRecord::InternalMetadata.table_exists? + def self.last_stored_environment + return nil if current_version == 0 + raise NoEnvironmentInSchemaError unless ActiveRecord::InternalMetadata.table_exists? - environment = ActiveRecord::InternalMetadata[:environment] - raise NoEnvironmentInSchemaError unless environment - environment - end + environment = ActiveRecord::InternalMetadata[:environment] + raise NoEnvironmentInSchemaError unless environment + environment + end - def self.current_environment - ActiveRecord::ConnectionHandling::DEFAULT_ENV.call - end + def self.current_environment + ActiveRecord::ConnectionHandling::DEFAULT_ENV.call + end - def self.protected_environment? - ActiveRecord::Base.protected_environments.include?(last_stored_environment) if last_stored_environment - end + def self.protected_environment? + ActiveRecord::Base.protected_environments.include?(last_stored_environment) if last_stored_environment + end - def up? - @direction == :up - end + def up? + @direction == :up + end - def down? - @direction == :down - end + def down? + @direction == :down + end # Wrap the migration in a transaction only if supported by the adapter. - def ddl_transaction(migration) - if use_transaction?(migration) - Base.transaction { yield } - else - yield + def ddl_transaction(migration) + if use_transaction?(migration) + Base.transaction { yield } + else + yield + end end - end - def use_transaction?(migration) - !migration.disable_ddl_transaction && Base.connection.supports_ddl_transactions? - end + def use_transaction?(migration) + !migration.disable_ddl_transaction && Base.connection.supports_ddl_transactions? + end - def use_advisory_lock? - Base.connection.supports_advisory_locks? - end + def use_advisory_lock? + Base.connection.supports_advisory_locks? + end - def with_advisory_lock - lock_id = generate_migrator_advisory_lock_id - got_lock = Base.connection.get_advisory_lock(lock_id) - raise ConcurrentMigrationError unless got_lock - load_migrated # reload schema_migrations to be sure it wasn't changed by another process before we got the lock - yield - ensure - Base.connection.release_advisory_lock(lock_id) if got_lock - end + def with_advisory_lock + lock_id = generate_migrator_advisory_lock_id + got_lock = Base.connection.get_advisory_lock(lock_id) + raise ConcurrentMigrationError unless got_lock + load_migrated # reload schema_migrations to be sure it wasn't changed by another process before we got the lock + yield + ensure + Base.connection.release_advisory_lock(lock_id) if got_lock + end - MIGRATOR_SALT = 2053462845 - def generate_migrator_advisory_lock_id - db_name_hash = Zlib.crc32(Base.connection.current_database) - MIGRATOR_SALT * db_name_hash - end + MIGRATOR_SALT = 2053462845 + def generate_migrator_advisory_lock_id + db_name_hash = Zlib.crc32(Base.connection.current_database) + MIGRATOR_SALT * db_name_hash + end end end diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb index 0fa665c7e0..6b2c5d8da5 100644 --- a/activerecord/lib/active_record/migration/command_recorder.rb +++ b/activerecord/lib/active_record/migration/command_recorder.rb @@ -112,127 +112,127 @@ module ActiveRecord private - module StraightReversions - private - { transaction: :transaction, - execute_block: :execute_block, - create_table: :drop_table, - create_join_table: :drop_join_table, - add_column: :remove_column, - add_timestamps: :remove_timestamps, - add_reference: :remove_reference, - enable_extension: :disable_extension - }.each do |cmd, inv| - [[inv, cmd], [cmd, inv]].uniq.each do |method, inverse| - class_eval <<-EOV, __FILE__, __LINE__ + 1 + module StraightReversions + private + { transaction: :transaction, + execute_block: :execute_block, + create_table: :drop_table, + create_join_table: :drop_join_table, + add_column: :remove_column, + add_timestamps: :remove_timestamps, + add_reference: :remove_reference, + enable_extension: :disable_extension + }.each do |cmd, inv| + [[inv, cmd], [cmd, inv]].uniq.each do |method, inverse| + class_eval <<-EOV, __FILE__, __LINE__ + 1 def invert_#{method}(args, &block) # def invert_create_table(args, &block) [:#{inverse}, args, block] # [:drop_table, args, block] end # end EOV - end + end + end end - end - include StraightReversions + include StraightReversions - def invert_drop_table(args, &block) - if args.size == 1 && block == nil - raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty)." + def invert_drop_table(args, &block) + if args.size == 1 && block == nil + raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty)." + end + super end - super - end - def invert_rename_table(args) - [:rename_table, args.reverse] - end + def invert_rename_table(args) + [:rename_table, args.reverse] + end - def invert_remove_column(args) - raise ActiveRecord::IrreversibleMigration, "remove_column is only reversible if given a type." if args.size <= 2 - super - end + def invert_remove_column(args) + raise ActiveRecord::IrreversibleMigration, "remove_column is only reversible if given a type." if args.size <= 2 + super + end - def invert_rename_index(args) - [:rename_index, [args.first] + args.last(2).reverse] - end + def invert_rename_index(args) + [:rename_index, [args.first] + args.last(2).reverse] + end - def invert_rename_column(args) - [:rename_column, [args.first] + args.last(2).reverse] - end + def invert_rename_column(args) + [:rename_column, [args.first] + args.last(2).reverse] + end - def invert_add_index(args) - table, columns, options = *args - options ||= {} + def invert_add_index(args) + table, columns, options = *args + options ||= {} - index_name = options[:name] - options_hash = index_name ? { name: index_name } : { column: columns } + index_name = options[:name] + options_hash = index_name ? { name: index_name } : { column: columns } - [:remove_index, [table, options_hash]] - end + [:remove_index, [table, options_hash]] + end - def invert_remove_index(args) - table, options_or_column = *args - if (options = options_or_column).is_a?(Hash) - unless options[:column] - raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option." + def invert_remove_index(args) + table, options_or_column = *args + if (options = options_or_column).is_a?(Hash) + unless options[:column] + raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option." + end + options = options.dup + [:add_index, [table, options.delete(:column), options]] + elsif (column = options_or_column).present? + [:add_index, [table, column]] end - options = options.dup - [:add_index, [table, options.delete(:column), options]] - elsif (column = options_or_column).present? - [:add_index, [table, column]] end - end - alias :invert_add_belongs_to :invert_add_reference - alias :invert_remove_belongs_to :invert_remove_reference + alias :invert_add_belongs_to :invert_add_reference + alias :invert_remove_belongs_to :invert_remove_reference + + def invert_change_column_default(args) + table, column, options = *args - def invert_change_column_default(args) - table, column, options = *args + unless options && options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to) + raise ActiveRecord::IrreversibleMigration, "change_column_default is only reversible if given a :from and :to option." + end - unless options && options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to) - raise ActiveRecord::IrreversibleMigration, "change_column_default is only reversible if given a :from and :to option." + [:change_column_default, [table, column, from: options[:to], to: options[:from]]] end - [:change_column_default, [table, column, from: options[:to], to: options[:from]]] - end + def invert_change_column_null(args) + args[2] = !args[2] + [:change_column_null, args] + end - def invert_change_column_null(args) - args[2] = !args[2] - [:change_column_null, args] - end + def invert_add_foreign_key(args) + from_table, to_table, add_options = args + add_options ||= {} - def invert_add_foreign_key(args) - from_table, to_table, add_options = args - add_options ||= {} + if add_options[:name] + options = { name: add_options[:name] } + elsif add_options[:column] + options = { column: add_options[:column] } + else + options = to_table + end - if add_options[:name] - options = { name: add_options[:name] } - elsif add_options[:column] - options = { column: add_options[:column] } - else - options = to_table + [:remove_foreign_key, [from_table, options]] end - [:remove_foreign_key, [from_table, options]] - end - - def invert_remove_foreign_key(args) - from_table, to_table, remove_options = args - raise ActiveRecord::IrreversibleMigration, "remove_foreign_key is only reversible if given a second table" if to_table.nil? || to_table.is_a?(Hash) + def invert_remove_foreign_key(args) + from_table, to_table, remove_options = args + raise ActiveRecord::IrreversibleMigration, "remove_foreign_key is only reversible if given a second table" if to_table.nil? || to_table.is_a?(Hash) - reversed_args = [from_table, to_table] - reversed_args << remove_options if remove_options + reversed_args = [from_table, to_table] + reversed_args << remove_options if remove_options - [:add_foreign_key, reversed_args] - end + [:add_foreign_key, reversed_args] + end # Forwards any missing method call to the \target. - def method_missing(method, *args, &block) - if @delegate.respond_to?(method) - @delegate.send(method, *args, &block) - else - super + def method_missing(method, *args, &block) + if @delegate.respond_to?(method) + @delegate.send(method, *args, &block) + else + super + end end - end end end end diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb index 5baca7183b..a79251f908 100644 --- a/activerecord/lib/active_record/migration/compatibility.rb +++ b/activerecord/lib/active_record/migration/compatibility.rb @@ -83,23 +83,23 @@ module ActiveRecord private - def index_name_for_remove(table_name, options = {}) - index_name = index_name(table_name, options) + def index_name_for_remove(table_name, options = {}) + index_name = index_name(table_name, options) - unless index_name_exists?(table_name, index_name, true) - if options.is_a?(Hash) && options.has_key?(:name) - options_without_column = options.dup - options_without_column.delete :column - index_name_without_column = index_name(table_name, options_without_column) + unless index_name_exists?(table_name, index_name, true) + if options.is_a?(Hash) && options.has_key?(:name) + options_without_column = options.dup + options_without_column.delete :column + index_name_without_column = index_name(table_name, options_without_column) - return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false) + return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false) + end + + raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist" end - raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist" + index_name end - - index_name - end end class V5_0 < V5_1 diff --git a/activerecord/lib/active_record/migration/join_table.rb b/activerecord/lib/active_record/migration/join_table.rb index 05569fadbd..89789f00ea 100644 --- a/activerecord/lib/active_record/migration/join_table.rb +++ b/activerecord/lib/active_record/migration/join_table.rb @@ -3,13 +3,13 @@ module ActiveRecord module JoinTable #:nodoc: private - def find_join_table_name(table_1, table_2, options = {}) - options.delete(:table_name) || join_table_name(table_1, table_2) - end + def find_join_table_name(table_1, table_2, options = {}) + options.delete(:table_name) || join_table_name(table_1, table_2) + end - def join_table_name(table_1, table_2) - ModelSchema.derive_join_table_name(table_1, table_2).to_sym - end + def join_table_name(table_1, table_2) + ModelSchema.derive_join_table_name(table_1, table_2).to_sym + end end end end diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 63312a8d11..1537fffc22 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -357,80 +357,80 @@ module ActiveRecord private - def schema_loaded? - defined?(@columns_hash) && @columns_hash - end + def schema_loaded? + defined?(@columns_hash) && @columns_hash + end - def load_schema - unless schema_loaded? - load_schema! + def load_schema + unless schema_loaded? + load_schema! + end end - end - def load_schema! - @columns_hash = connection.schema_cache.columns_hash(table_name).except(*ignored_columns) - @columns_hash.each do |name, column| - warn_if_deprecated_type(column) - define_attribute( - name, - connection.lookup_cast_type_from_column(column), - default: column.default, - user_provided_default: false - ) + def load_schema! + @columns_hash = connection.schema_cache.columns_hash(table_name).except(*ignored_columns) + @columns_hash.each do |name, column| + warn_if_deprecated_type(column) + define_attribute( + name, + connection.lookup_cast_type_from_column(column), + default: column.default, + user_provided_default: false + ) + end end - end - def reload_schema_from_cache - @arel_engine = nil - @arel_table = nil - @column_names = nil - @attribute_types = nil - @content_columns = nil - @default_attributes = nil - @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column - @attributes_builder = nil - @columns = nil - @columns_hash = nil - @attribute_names = nil - @yaml_encoder = nil - direct_descendants.each do |descendant| - descendant.send(:reload_schema_from_cache) + def reload_schema_from_cache + @arel_engine = nil + @arel_table = nil + @column_names = nil + @attribute_types = nil + @content_columns = nil + @default_attributes = nil + @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column + @attributes_builder = nil + @columns = nil + @columns_hash = nil + @attribute_names = nil + @yaml_encoder = nil + direct_descendants.each do |descendant| + descendant.send(:reload_schema_from_cache) + end end - end # Guesses the table name, but does not decorate it with prefix and suffix information. - def undecorated_table_name(class_name = base_class.name) - table_name = class_name.to_s.demodulize.underscore - pluralize_table_names ? table_name.pluralize : table_name - end + def undecorated_table_name(class_name = base_class.name) + table_name = class_name.to_s.demodulize.underscore + pluralize_table_names ? table_name.pluralize : table_name + end # Computes and returns a table name according to default conventions. - def compute_table_name - base = base_class - if self == base - # Nested classes are prefixed with singular parent table name. - if parent < Base && !parent.abstract_class? - contained = parent.table_name - contained = contained.singularize if parent.pluralize_table_names - contained += "_" + def compute_table_name + base = base_class + if self == base + # Nested classes are prefixed with singular parent table name. + if parent < Base && !parent.abstract_class? + contained = parent.table_name + contained = contained.singularize if parent.pluralize_table_names + contained += "_" + end + + "#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{full_table_name_suffix}" + else + # STI subclasses always use their superclass' table. + base.table_name end - - "#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{full_table_name_suffix}" - else - # STI subclasses always use their superclass' table. - base.table_name end - end - def warn_if_deprecated_type(column) - return if attributes_to_define_after_schema_loads.key?(column.name) - if column.respond_to?(:oid) && column.sql_type.start_with?("point") - if column.array? - array_arguments = ", array: true" - else - array_arguments = "" - end - ActiveSupport::Deprecation.warn(<<-WARNING.strip_heredoc) + def warn_if_deprecated_type(column) + return if attributes_to_define_after_schema_loads.key?(column.name) + if column.respond_to?(:oid) && column.sql_type.start_with?("point") + if column.array? + array_arguments = ", array: true" + else + array_arguments = "" + end + ActiveSupport::Deprecation.warn(<<-WARNING.strip_heredoc) The behavior of the `:point` type will be changing in Rails 5.1 to return a `Point` object, rather than an `Array`. If you'd like to keep the old behavior, you can add this line to #{self.name}: @@ -441,8 +441,8 @@ module ActiveRecord attribute :#{column.name}, :point#{array_arguments} WARNING + end end - end end end end diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index c9fe1167e8..682b404060 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -352,8 +352,8 @@ module ActiveRecord # This redirects the attempts to write objects in an association through # the helper methods defined below. Makes it seem like the nested # associations are just regular associations. - def generate_association_writer(association_name, type) - generated_association_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1 + def generate_association_writer(association_name, type) + generated_association_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1 if method_defined?(:#{association_name}_attributes=) remove_method(:#{association_name}_attributes=) end @@ -361,7 +361,7 @@ module ActiveRecord assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes) end eoruby - end + end end # Returns ActiveRecord::AutosaveAssociation::marked_for_destruction? It's @@ -377,7 +377,7 @@ module ActiveRecord # Attribute hash keys that should not be assigned as normal attributes. # These hash keys are nested attributes implementation details. - UNASSIGNABLE_KEYS = %w( id _destroy ) + UNASSIGNABLE_KEYS = %w( id _destroy ) # Assigns the given attributes to the association. # @@ -392,37 +392,37 @@ module ActiveRecord # If the given attributes include a matching <tt>:id</tt> attribute, or # update_only is true, and a <tt>:_destroy</tt> key set to a truthy value, # then the existing record will be marked for destruction. - def assign_nested_attributes_for_one_to_one_association(association_name, attributes) - options = self.nested_attributes_options[association_name] - if attributes.respond_to?(:permitted?) - attributes = attributes.to_h - end - attributes = attributes.with_indifferent_access - existing_record = send(association_name) + def assign_nested_attributes_for_one_to_one_association(association_name, attributes) + options = self.nested_attributes_options[association_name] + if attributes.respond_to?(:permitted?) + attributes = attributes.to_h + end + attributes = attributes.with_indifferent_access + existing_record = send(association_name) - if (options[:update_only] || !attributes["id"].blank?) && existing_record && - (options[:update_only] || existing_record.id.to_s == attributes["id"].to_s) - assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes) + if (options[:update_only] || !attributes["id"].blank?) && existing_record && + (options[:update_only] || existing_record.id.to_s == attributes["id"].to_s) + assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes) - elsif attributes["id"].present? - raise_nested_attributes_record_not_found!(association_name, attributes["id"]) + elsif attributes["id"].present? + raise_nested_attributes_record_not_found!(association_name, attributes["id"]) - elsif !reject_new_record?(association_name, attributes) - assignable_attributes = attributes.except(*UNASSIGNABLE_KEYS) + elsif !reject_new_record?(association_name, attributes) + assignable_attributes = attributes.except(*UNASSIGNABLE_KEYS) - if existing_record && existing_record.new_record? - existing_record.assign_attributes(assignable_attributes) - association(association_name).initialize_attributes(existing_record) - else - method = "build_#{association_name}" - if respond_to?(method) - send(method, assignable_attributes) + if existing_record && existing_record.new_record? + existing_record.assign_attributes(assignable_attributes) + association(association_name).initialize_attributes(existing_record) else - raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?" + method = "build_#{association_name}" + if respond_to?(method) + send(method, assignable_attributes) + else + raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?" + end end end end - end # Assigns the given attributes to the collection association. # @@ -451,65 +451,65 @@ module ActiveRecord # { name: 'John' }, # { id: '2', _destroy: true } # ]) - def assign_nested_attributes_for_collection_association(association_name, attributes_collection) - options = self.nested_attributes_options[association_name] - if attributes_collection.respond_to?(:permitted?) - attributes_collection = attributes_collection.to_h - end + def assign_nested_attributes_for_collection_association(association_name, attributes_collection) + options = self.nested_attributes_options[association_name] + if attributes_collection.respond_to?(:permitted?) + attributes_collection = attributes_collection.to_h + end - unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array) - raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})" - end + unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array) + raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})" + end - check_record_limit!(options[:limit], attributes_collection) + check_record_limit!(options[:limit], attributes_collection) - if attributes_collection.is_a? Hash - keys = attributes_collection.keys - attributes_collection = if keys.include?("id") || keys.include?(:id) - [attributes_collection] - else - attributes_collection.values + if attributes_collection.is_a? Hash + keys = attributes_collection.keys + attributes_collection = if keys.include?("id") || keys.include?(:id) + [attributes_collection] + else + attributes_collection.values + end end - end - - association = association(association_name) - existing_records = if association.loaded? - association.target - else - attribute_ids = attributes_collection.map {|a| a["id"] || a[:id] }.compact - attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids) - end + association = association(association_name) - attributes_collection.each do |attributes| - if attributes.respond_to?(:permitted?) - attributes = attributes.to_h + existing_records = if association.loaded? + association.target + else + attribute_ids = attributes_collection.map {|a| a["id"] || a[:id] }.compact + attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids) end - attributes = attributes.with_indifferent_access - if attributes["id"].blank? - unless reject_new_record?(association_name, attributes) - association.build(attributes.except(*UNASSIGNABLE_KEYS)) + attributes_collection.each do |attributes| + if attributes.respond_to?(:permitted?) + attributes = attributes.to_h end - elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes["id"].to_s } - unless call_reject_if(association_name, attributes) - # Make sure we are operating on the actual object which is in the association's - # proxy_target array (either by finding it, or adding it if not found) - # Take into account that the proxy_target may have changed due to callbacks - target_record = association.target.detect { |record| record.id.to_s == attributes["id"].to_s } - if target_record - existing_record = target_record - else - association.add_to_target(existing_record, :skip_callbacks) - end + attributes = attributes.with_indifferent_access - assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) + if attributes["id"].blank? + unless reject_new_record?(association_name, attributes) + association.build(attributes.except(*UNASSIGNABLE_KEYS)) + end + elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes["id"].to_s } + unless call_reject_if(association_name, attributes) + # Make sure we are operating on the actual object which is in the association's + # proxy_target array (either by finding it, or adding it if not found) + # Take into account that the proxy_target may have changed due to callbacks + target_record = association.target.detect { |record| record.id.to_s == attributes["id"].to_s } + if target_record + existing_record = target_record + else + association.add_to_target(existing_record, :skip_callbacks) + end + + assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) + end + else + raise_nested_attributes_record_not_found!(association_name, attributes["id"]) end - else - raise_nested_attributes_record_not_found!(association_name, attributes["id"]) end end - end # Takes in a limit and checks if the attributes_collection has too many # records. It accepts limit in the form of symbol, proc, or @@ -517,71 +517,71 @@ module ActiveRecord # # Raises TooManyRecords error if the attributes_collection is # larger than the limit. - def check_record_limit!(limit, attributes_collection) - if limit - limit = case limit - when Symbol - send(limit) - when Proc - limit.call - else - limit - end + def check_record_limit!(limit, attributes_collection) + if limit + limit = case limit + when Symbol + send(limit) + when Proc + limit.call + else + limit + end - if limit && attributes_collection.size > limit - raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead." + if limit && attributes_collection.size > limit + raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead." + end end end - end # Updates a record with the +attributes+ or marks it for destruction if # +allow_destroy+ is +true+ and has_destroy_flag? returns +true+. - def assign_to_or_mark_for_destruction(record, attributes, allow_destroy) - record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS)) - record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy - end + def assign_to_or_mark_for_destruction(record, attributes, allow_destroy) + record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS)) + record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy + end # Determines if a hash contains a truthy _destroy key. - def has_destroy_flag?(hash) - Type::Boolean.new.cast(hash["_destroy"]) - end + def has_destroy_flag?(hash) + Type::Boolean.new.cast(hash["_destroy"]) + end # Determines if a new record should be rejected by checking # has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this # association and evaluates to +true+. - def reject_new_record?(association_name, attributes) - will_be_destroyed?(association_name, attributes) || call_reject_if(association_name, attributes) - end + def reject_new_record?(association_name, attributes) + will_be_destroyed?(association_name, attributes) || call_reject_if(association_name, attributes) + end # Determines if a record with the particular +attributes+ should be # rejected by calling the reject_if Symbol or Proc (if defined). # The reject_if option is defined by +accepts_nested_attributes_for+. # # Returns false if there is a +destroy_flag+ on the attributes. - def call_reject_if(association_name, attributes) - return false if will_be_destroyed?(association_name, attributes) - - case callback = self.nested_attributes_options[association_name][:reject_if] - when Symbol - method(callback).arity == 0 ? send(callback) : send(callback, attributes) - when Proc - callback.call(attributes) + def call_reject_if(association_name, attributes) + return false if will_be_destroyed?(association_name, attributes) + + case callback = self.nested_attributes_options[association_name][:reject_if] + when Symbol + method(callback).arity == 0 ? send(callback) : send(callback, attributes) + when Proc + callback.call(attributes) + end end - end # Only take into account the destroy flag if <tt>:allow_destroy</tt> is true - def will_be_destroyed?(association_name, attributes) - allow_destroy?(association_name) && has_destroy_flag?(attributes) - end + def will_be_destroyed?(association_name, attributes) + allow_destroy?(association_name) && has_destroy_flag?(attributes) + end - def allow_destroy?(association_name) - self.nested_attributes_options[association_name][:allow_destroy] - end + def allow_destroy?(association_name) + self.nested_attributes_options[association_name][:allow_destroy] + end - def raise_nested_attributes_record_not_found!(association_name, record_id) - model = self.class._reflect_on_association(association_name).klass.name - raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}", - model, "id", record_id) - end + def raise_nested_attributes_record_not_found!(association_name, record_id) + model = self.class._reflect_on_association(association_name).klass.name + raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}", + model, "id", record_id) + end end end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 3532465fee..6f0dbeb9e5 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -204,13 +204,13 @@ db_namespace = namespace :db do base_dir = ActiveRecord::Tasks::DatabaseTasks.fixtures_path fixtures_dir = if ENV["FIXTURES_DIR"] - File.join base_dir, ENV["FIXTURES_DIR"] + File.join base_dir, ENV["FIXTURES_DIR"] else base_dir end fixture_files = if ENV["FIXTURES"] - ENV["FIXTURES"].split(",") + ENV["FIXTURES"].split(",") else # The use of String#[] here is to support namespaced fixtures Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] } diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index e96adc9af8..60ccb73d50 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -685,48 +685,48 @@ module ActiveRecord private - def exec_queries - @records = eager_loading? ? find_with_associations.freeze : @klass.find_by_sql(arel, bound_attributes).freeze - - preload = preload_values - preload += includes_values unless eager_loading? - preloader = build_preloader - preload.each do |associations| - preloader.preload @records, associations - end + def exec_queries + @records = eager_loading? ? find_with_associations.freeze : @klass.find_by_sql(arel, bound_attributes).freeze + + preload = preload_values + preload += includes_values unless eager_loading? + preloader = build_preloader + preload.each do |associations| + preloader.preload @records, associations + end - @records.each(&:readonly!) if readonly_value + @records.each(&:readonly!) if readonly_value - @loaded = true - @records - end + @loaded = true + @records + end - def build_preloader - ActiveRecord::Associations::Preloader.new - end + def build_preloader + ActiveRecord::Associations::Preloader.new + end - def references_eager_loaded_tables? - joined_tables = arel.join_sources.map do |join| - if join.is_a?(Arel::Nodes::StringJoin) - tables_in_string(join.left) - else - [join.left.table_name, join.left.table_alias] + def references_eager_loaded_tables? + joined_tables = arel.join_sources.map do |join| + if join.is_a?(Arel::Nodes::StringJoin) + tables_in_string(join.left) + else + [join.left.table_name, join.left.table_alias] + end end - end - joined_tables += [table.name, table.table_alias] + joined_tables += [table.name, table.table_alias] - # always convert table names to downcase as in Oracle quoted table names are in uppercase - joined_tables = joined_tables.flatten.compact.map(&:downcase).uniq + # always convert table names to downcase as in Oracle quoted table names are in uppercase + joined_tables = joined_tables.flatten.compact.map(&:downcase).uniq - (references_values - joined_tables).any? - end + (references_values - joined_tables).any? + end - def tables_in_string(string) - return [] if string.blank? - # always convert table names to downcase as in Oracle quoted table names are in uppercase - # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries - string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map(&:downcase).uniq - ["raw_sql_"] - end + def tables_in_string(string) + return [] if string.blank? + # always convert table names to downcase as in Oracle quoted table names are in uppercase + # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries + string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map(&:downcase).uniq - ["raw_sql_"] + end end end diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index f4dd106edf..4b2987ac6d 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -249,24 +249,24 @@ module ActiveRecord private - def apply_limits(relation, start, finish) - relation = relation.where(arel_attribute(primary_key).gteq(start)) if start - relation = relation.where(arel_attribute(primary_key).lteq(finish)) if finish - relation - end + def apply_limits(relation, start, finish) + relation = relation.where(arel_attribute(primary_key).gteq(start)) if start + relation = relation.where(arel_attribute(primary_key).lteq(finish)) if finish + relation + end - def batch_order - "#{quoted_table_name}.#{quoted_primary_key} ASC" - end + def batch_order + "#{quoted_table_name}.#{quoted_primary_key} ASC" + end - def act_on_ignored_order(error_on_ignore) - raise_error = (error_on_ignore.nil? ? self.klass.error_on_ignored_order : error_on_ignore) + def act_on_ignored_order(error_on_ignore) + raise_error = (error_on_ignore.nil? ? self.klass.error_on_ignored_order : error_on_ignore) - if raise_error - raise ArgumentError.new(ORDER_IGNORE_MESSAGE) - elsif logger - logger.warn(ORDER_IGNORE_MESSAGE) + if raise_error + raise ArgumentError.new(ORDER_IGNORE_MESSAGE) + elsif logger + logger.warn(ORDER_IGNORE_MESSAGE) + end end - end end end diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 4af9f0859a..233a0622d5 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -185,147 +185,147 @@ module ActiveRecord private - def has_include?(column_name) - eager_loading? || (includes_values.present? && column_name && column_name != :all) - end + def has_include?(column_name) + eager_loading? || (includes_values.present? && column_name && column_name != :all) + end - def perform_calculation(operation, column_name) - operation = operation.to_s.downcase + def perform_calculation(operation, column_name) + operation = operation.to_s.downcase - # If #count is used with #distinct (i.e. `relation.distinct.count`) it is - # considered distinct. - distinct = self.distinct_value + # If #count is used with #distinct (i.e. `relation.distinct.count`) it is + # considered distinct. + distinct = self.distinct_value - if operation == "count" - column_name ||= select_for_count + if operation == "count" + column_name ||= select_for_count + + unless arel.ast.grep(Arel::Nodes::OuterJoin).empty? + distinct = true + end - unless arel.ast.grep(Arel::Nodes::OuterJoin).empty? - distinct = true + column_name = primary_key if column_name == :all && distinct + distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i end - column_name = primary_key if column_name == :all && distinct - distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i + if group_values.any? + execute_grouped_calculation(operation, column_name, distinct) + else + execute_simple_calculation(operation, column_name, distinct) + end end - if group_values.any? - execute_grouped_calculation(operation, column_name, distinct) - else - execute_simple_calculation(operation, column_name, distinct) - end - end + def aggregate_column(column_name) + return column_name if Arel::Expressions === column_name - def aggregate_column(column_name) - return column_name if Arel::Expressions === column_name + if @klass.column_names.include?(column_name.to_s) + Arel::Attribute.new(@klass.unscoped.table, column_name) + else + Arel.sql(column_name == :all ? "*" : column_name.to_s) + end + end - if @klass.column_names.include?(column_name.to_s) - Arel::Attribute.new(@klass.unscoped.table, column_name) - else - Arel.sql(column_name == :all ? "*" : column_name.to_s) + def operation_over_aggregate_column(column, operation, distinct) + operation == "count" ? column.count(distinct) : column.send(operation) end - end - def operation_over_aggregate_column(column, operation, distinct) - operation == "count" ? column.count(distinct) : column.send(operation) - end + def execute_simple_calculation(operation, column_name, distinct) #:nodoc: + # PostgreSQL doesn't like ORDER BY when there are no GROUP BY + relation = unscope(:order) - def execute_simple_calculation(operation, column_name, distinct) #:nodoc: - # PostgreSQL doesn't like ORDER BY when there are no GROUP BY - relation = unscope(:order) + column_alias = column_name - column_alias = column_name + if operation == "count" && (relation.limit_value || relation.offset_value) + # Shortcut when limit is zero. + return 0 if relation.limit_value == 0 - if operation == "count" && (relation.limit_value || relation.offset_value) - # Shortcut when limit is zero. - return 0 if relation.limit_value == 0 + query_builder = build_count_subquery(relation, column_name, distinct) + else + column = aggregate_column(column_name) - query_builder = build_count_subquery(relation, column_name, distinct) - else - column = aggregate_column(column_name) + select_value = operation_over_aggregate_column(column, operation, distinct) - select_value = operation_over_aggregate_column(column, operation, distinct) + column_alias = select_value.alias + column_alias ||= @klass.connection.column_name_for_operation(operation, select_value) + relation.select_values = [select_value] - column_alias = select_value.alias - column_alias ||= @klass.connection.column_name_for_operation(operation, select_value) - relation.select_values = [select_value] + query_builder = relation.arel + end - query_builder = relation.arel - end + result = @klass.connection.select_all(query_builder, nil, bound_attributes) + row = result.first + value = row && row.values.first + column = result.column_types.fetch(column_alias) do + type_for(column_name) + end - result = @klass.connection.select_all(query_builder, nil, bound_attributes) - row = result.first - value = row && row.values.first - column = result.column_types.fetch(column_alias) do - type_for(column_name) + type_cast_calculated_value(value, column, operation) end - type_cast_calculated_value(value, column, operation) - end + def execute_grouped_calculation(operation, column_name, distinct) #:nodoc: + group_attrs = group_values - def execute_grouped_calculation(operation, column_name, distinct) #:nodoc: - group_attrs = group_values - - if group_attrs.first.respond_to?(:to_sym) - association = @klass._reflect_on_association(group_attrs.first) - associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations - group_fields = Array(associated ? association.foreign_key : group_attrs) - else - group_fields = group_attrs - end - group_fields = arel_columns(group_fields) + if group_attrs.first.respond_to?(:to_sym) + association = @klass._reflect_on_association(group_attrs.first) + associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations + group_fields = Array(associated ? association.foreign_key : group_attrs) + else + group_fields = group_attrs + end + group_fields = arel_columns(group_fields) - group_aliases = group_fields.map { |field| column_alias_for(field) } - group_columns = group_aliases.zip(group_fields) + group_aliases = group_fields.map { |field| column_alias_for(field) } + group_columns = group_aliases.zip(group_fields) - if operation == "count" && column_name == :all - aggregate_alias = "count_all" - else - aggregate_alias = column_alias_for([operation, column_name].join(" ")) - end - - select_values = [ - operation_over_aggregate_column( - aggregate_column(column_name), - operation, - distinct).as(aggregate_alias) - ] - select_values += select_values unless having_clause.empty? - - select_values.concat group_columns.map { |aliaz, field| - if field.respond_to?(:as) - field.as(aliaz) + if operation == "count" && column_name == :all + aggregate_alias = "count_all" else - "#{field} AS #{aliaz}" + aggregate_alias = column_alias_for([operation, column_name].join(" ")) end - } - relation = except(:group) - relation.group_values = group_fields - relation.select_values = select_values + select_values = [ + operation_over_aggregate_column( + aggregate_column(column_name), + operation, + distinct).as(aggregate_alias) + ] + select_values += select_values unless having_clause.empty? + + select_values.concat group_columns.map { |aliaz, field| + if field.respond_to?(:as) + field.as(aliaz) + else + "#{field} AS #{aliaz}" + end + } - calculated_data = @klass.connection.select_all(relation, nil, relation.bound_attributes) + relation = except(:group) + relation.group_values = group_fields + relation.select_values = select_values - if association - key_ids = calculated_data.collect { |row| row[group_aliases.first] } - key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids) - key_records = Hash[key_records.map { |r| [r.id, r] }] - end + calculated_data = @klass.connection.select_all(relation, nil, relation.bound_attributes) - Hash[calculated_data.map do |row| - key = group_columns.map { |aliaz, col_name| - column = type_for(col_name) do - calculated_data.column_types.fetch(aliaz) do - Type::Value.new - end - end - type_cast_calculated_value(row[aliaz], column) - } - key = key.first if key.size == 1 - key = key_records[key] if associated + if association + key_ids = calculated_data.collect { |row| row[group_aliases.first] } + key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids) + key_records = Hash[key_records.map { |r| [r.id, r] }] + end - column_type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) } - [key, type_cast_calculated_value(row[aggregate_alias], column_type, operation)] - end] - end + Hash[calculated_data.map do |row| + key = group_columns.map { |aliaz, col_name| + column = type_for(col_name) do + calculated_data.column_types.fetch(aliaz) do + Type::Value.new + end + end + type_cast_calculated_value(row[aliaz], column) + } + key = key.first if key.size == 1 + key = key_records[key] if associated + + column_type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) } + [key, type_cast_calculated_value(row[aggregate_alias], column_type, operation)] + end] + end # Converts the given keys to the value that the database adapter returns as # a usable column name: @@ -334,54 +334,54 @@ module ActiveRecord # column_alias_for("sum(id)") # => "sum_id" # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id" # column_alias_for("count(*)") # => "count_all" - def column_alias_for(keys) - if keys.respond_to? :name - keys = "#{keys.relation.name}.#{keys.name}" - end + def column_alias_for(keys) + if keys.respond_to? :name + keys = "#{keys.relation.name}.#{keys.name}" + end - table_name = keys.to_s.downcase - table_name.gsub!(/\*/, "all") - table_name.gsub!(/\W+/, " ") - table_name.strip! - table_name.gsub!(/ +/, "_") + table_name = keys.to_s.downcase + table_name.gsub!(/\*/, "all") + table_name.gsub!(/\W+/, " ") + table_name.strip! + table_name.gsub!(/ +/, "_") - @klass.connection.table_alias_for(table_name) - end + @klass.connection.table_alias_for(table_name) + end - def type_for(field, &block) - field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last - @klass.type_for_attribute(field_name, &block) - end + def type_for(field, &block) + field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last + @klass.type_for_attribute(field_name, &block) + end - def type_cast_calculated_value(value, type, operation = nil) - case operation - when "count" then value.to_i - when "sum" then type.deserialize(value || 0) - when "average" then value.respond_to?(:to_d) ? value.to_d : value - else type.deserialize(value) + def type_cast_calculated_value(value, type, operation = nil) + case operation + when "count" then value.to_i + when "sum" then type.deserialize(value || 0) + when "average" then value.respond_to?(:to_d) ? value.to_d : value + else type.deserialize(value) + end end - end - def select_for_count - if select_values.present? - return select_values.first if select_values.one? - select_values.join(", ") - else - :all + def select_for_count + if select_values.present? + return select_values.first if select_values.one? + select_values.join(", ") + else + :all + end end - end - def build_count_subquery(relation, column_name, distinct) - column_alias = Arel.sql("count_column") - subquery_alias = Arel.sql("subquery_for_count") + def build_count_subquery(relation, column_name, distinct) + column_alias = Arel.sql("count_column") + subquery_alias = Arel.sql("subquery_for_count") - aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias) - relation.select_values = [aliased_column] - subquery = relation.arel.as(subquery_alias) + aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias) + relation.select_values = [aliased_column] + subquery = relation.arel.as(subquery_alias) - sm = Arel::SelectManager.new relation.engine - select_value = operation_over_aggregate_column(column_alias, "count", distinct) - sm.project(select_value).from(subquery) - end + sm = Arel::SelectManager.new relation.engine + select_value = operation_over_aggregate_column(column_alias, "count", distinct) + sm.project(select_value).from(subquery) + end end end diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 6b28d85a49..b4147ce434 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -83,17 +83,17 @@ module ActiveRecord protected - def method_missing(method, *args, &block) - if @klass.respond_to?(method) - self.class.delegate_to_scoped_klass(method) - scoping { @klass.public_send(method, *args, &block) } - elsif arel.respond_to?(method) - self.class.delegate method, to: :arel - arel.public_send(method, *args, &block) - else - super + def method_missing(method, *args, &block) + if @klass.respond_to?(method) + self.class.delegate_to_scoped_klass(method) + scoping { @klass.public_send(method, *args, &block) } + elsif arel.respond_to?(method) + self.class.delegate method, to: :arel + arel.public_send(method, *args, &block) + else + super + end end - end end module ClassMethods # :nodoc: @@ -103,9 +103,9 @@ module ActiveRecord private - def relation_class_for(klass) - klass.relation_delegate_class(self) - end + def relation_class_for(klass) + klass.relation_delegate_class(self) + end end def respond_to?(method, include_private = false) @@ -115,14 +115,14 @@ module ActiveRecord protected - def method_missing(method, *args, &block) - if @klass.respond_to?(method) - scoping { @klass.public_send(method, *args, &block) } - elsif arel.respond_to?(method) - arel.public_send(method, *args, &block) - else - super + def method_missing(method, *args, &block) + if @klass.respond_to?(method) + scoping { @klass.public_send(method, *args, &block) } + elsif arel.respond_to?(method) + arel.public_send(method, *args, &block) + else + super + end end - end end end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 6241e0dbdb..6349701dba 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -361,232 +361,232 @@ module ActiveRecord private - def offset_index - offset_value || 0 - end + def offset_index + offset_value || 0 + end - def find_with_associations - # NOTE: the JoinDependency constructed here needs to know about - # any joins already present in `self`, so pass them in - # - # failing to do so means that in cases like activerecord/test/cases/associations/inner_join_association_test.rb:136 - # incorrect SQL is generated. In that case, the join dependency for - # SpecialCategorizations is constructed without knowledge of the - # preexisting join in joins_values to categorizations (by way of - # the `has_many :through` for categories). - # - join_dependency = construct_join_dependency(joins_values) - - aliases = join_dependency.aliases - relation = select aliases.columns - relation = apply_join_dependency(relation, join_dependency) - - if block_given? - yield relation - else - if ActiveRecord::NullRelation === relation - [] + def find_with_associations + # NOTE: the JoinDependency constructed here needs to know about + # any joins already present in `self`, so pass them in + # + # failing to do so means that in cases like activerecord/test/cases/associations/inner_join_association_test.rb:136 + # incorrect SQL is generated. In that case, the join dependency for + # SpecialCategorizations is constructed without knowledge of the + # preexisting join in joins_values to categorizations (by way of + # the `has_many :through` for categories). + # + join_dependency = construct_join_dependency(joins_values) + + aliases = join_dependency.aliases + relation = select aliases.columns + relation = apply_join_dependency(relation, join_dependency) + + if block_given? + yield relation else - arel = relation.arel - rows = connection.select_all(arel, "SQL", relation.bound_attributes) - join_dependency.instantiate(rows, aliases) + if ActiveRecord::NullRelation === relation + [] + else + arel = relation.arel + rows = connection.select_all(arel, "SQL", relation.bound_attributes) + join_dependency.instantiate(rows, aliases) + end end end - end - def construct_join_dependency(joins = [], eager_loading: true) - including = eager_load_values + includes_values - ActiveRecord::Associations::JoinDependency.new(@klass, including, joins, eager_loading: eager_loading) - end + def construct_join_dependency(joins = [], eager_loading: true) + including = eager_load_values + includes_values + ActiveRecord::Associations::JoinDependency.new(@klass, including, joins, eager_loading: eager_loading) + end - def construct_relation_for_association_calculations - apply_join_dependency(self, construct_join_dependency(joins_values)) - end + def construct_relation_for_association_calculations + apply_join_dependency(self, construct_join_dependency(joins_values)) + end - def apply_join_dependency(relation, join_dependency) - relation = relation.except(:includes, :eager_load, :preload) - relation = relation.joins join_dependency + def apply_join_dependency(relation, join_dependency) + relation = relation.except(:includes, :eager_load, :preload) + relation = relation.joins join_dependency - if using_limitable_reflections?(join_dependency.reflections) - relation - else - if relation.limit_value - limited_ids = limited_ids_for(relation) - limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids) + if using_limitable_reflections?(join_dependency.reflections) + relation + else + if relation.limit_value + limited_ids = limited_ids_for(relation) + limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids) + end + relation.except(:limit, :offset) end - relation.except(:limit, :offset) end - end - def limited_ids_for(relation) - values = @klass.connection.columns_for_distinct( - "#{quoted_table_name}.#{quoted_primary_key}", relation.order_values) + def limited_ids_for(relation) + values = @klass.connection.columns_for_distinct( + "#{quoted_table_name}.#{quoted_primary_key}", relation.order_values) - relation = relation.except(:select).select(values).distinct! - arel = relation.arel + relation = relation.except(:select).select(values).distinct! + arel = relation.arel - id_rows = @klass.connection.select_all(arel, "SQL", relation.bound_attributes) - id_rows.map {|row| row[primary_key]} - end + id_rows = @klass.connection.select_all(arel, "SQL", relation.bound_attributes) + id_rows.map {|row| row[primary_key]} + end - def using_limitable_reflections?(reflections) - reflections.none?(&:collection?) - end + def using_limitable_reflections?(reflections) + reflections.none?(&:collection?) + end protected - def find_with_ids(*ids) - raise UnknownPrimaryKey.new(@klass) if primary_key.nil? + def find_with_ids(*ids) + raise UnknownPrimaryKey.new(@klass) if primary_key.nil? - expects_array = ids.first.kind_of?(Array) - return ids.first if expects_array && ids.first.empty? + expects_array = ids.first.kind_of?(Array) + return ids.first if expects_array && ids.first.empty? - ids = ids.flatten.compact.uniq + ids = ids.flatten.compact.uniq - case ids.size - when 0 - raise RecordNotFound, "Couldn't find #{@klass.name} without an ID" - when 1 - result = find_one(ids.first) - expects_array ? [ result ] : result - else - find_some(ids) + case ids.size + when 0 + raise RecordNotFound, "Couldn't find #{@klass.name} without an ID" + when 1 + result = find_one(ids.first) + expects_array ? [ result ] : result + else + find_some(ids) + end + rescue RangeError + raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID" end - rescue RangeError - raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID" - end - def find_one(id) - if ActiveRecord::Base === id - id = id.id - ActiveSupport::Deprecation.warn(<<-MSG.squish) + def find_one(id) + if ActiveRecord::Base === id + id = id.id + ActiveSupport::Deprecation.warn(<<-MSG.squish) You are passing an instance of ActiveRecord::Base to `find`. Please pass the id of the object by calling `.id`. MSG - end + end - relation = where(primary_key => id) - record = relation.take + relation = where(primary_key => id) + record = relation.take - raise_record_not_found_exception!(id, 0, 1) unless record + raise_record_not_found_exception!(id, 0, 1) unless record - record - end + record + end - def find_some(ids) - return find_some_ordered(ids) unless order_values.present? + def find_some(ids) + return find_some_ordered(ids) unless order_values.present? - result = where(primary_key => ids).to_a + result = where(primary_key => ids).to_a - expected_size = - if limit_value && ids.size > limit_value - limit_value - else - ids.size - end + expected_size = + if limit_value && ids.size > limit_value + limit_value + else + ids.size + end - # 11 ids with limit 3, offset 9 should give 2 results. - if offset_value && (ids.size - offset_value < expected_size) - expected_size = ids.size - offset_value - end + # 11 ids with limit 3, offset 9 should give 2 results. + if offset_value && (ids.size - offset_value < expected_size) + expected_size = ids.size - offset_value + end - if result.size == expected_size - result - else - raise_record_not_found_exception!(ids, result.size, expected_size) + if result.size == expected_size + result + else + raise_record_not_found_exception!(ids, result.size, expected_size) + end end - end - def find_some_ordered(ids) - ids = ids.slice(offset_value || 0, limit_value || ids.size) || [] + def find_some_ordered(ids) + ids = ids.slice(offset_value || 0, limit_value || ids.size) || [] - result = except(:limit, :offset).where(primary_key => ids).records + result = except(:limit, :offset).where(primary_key => ids).records - if result.size == ids.size - pk_type = @klass.type_for_attribute(primary_key) + if result.size == ids.size + pk_type = @klass.type_for_attribute(primary_key) - records_by_id = result.index_by(&:id) - ids.map { |id| records_by_id.fetch(pk_type.cast(id)) } - else - raise_record_not_found_exception!(ids, result.size, ids.size) + records_by_id = result.index_by(&:id) + ids.map { |id| records_by_id.fetch(pk_type.cast(id)) } + else + raise_record_not_found_exception!(ids, result.size, ids.size) + end end - end - def find_take - if loaded? - records.first - else - @take ||= limit(1).records.first + def find_take + if loaded? + records.first + else + @take ||= limit(1).records.first + end end - end - def find_nth(index, offset = nil) - # TODO: once the offset argument is removed we rely on offset_index - # within find_nth_with_limit, rather than pass it in via - # find_nth_with_limit_and_offset - if offset - ActiveSupport::Deprecation.warn(<<-MSG.squish) + def find_nth(index, offset = nil) + # TODO: once the offset argument is removed we rely on offset_index + # within find_nth_with_limit, rather than pass it in via + # find_nth_with_limit_and_offset + if offset + ActiveSupport::Deprecation.warn(<<-MSG.squish) Passing an offset argument to find_nth is deprecated, please use Relation#offset instead. MSG + end + if loaded? + records[index] + else + offset ||= offset_index + @offsets[offset + index] ||= find_nth_with_limit_and_offset(index, 1, offset: offset).first + end end - if loaded? - records[index] - else - offset ||= offset_index - @offsets[offset + index] ||= find_nth_with_limit_and_offset(index, 1, offset: offset).first - end - end - def find_nth!(index) - find_nth(index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]") - end - - def find_nth_with_limit(index, limit) - # TODO: once the offset argument is removed from find_nth, - # find_nth_with_limit_and_offset can be merged into this method - relation = if order_values.empty? && primary_key - order(arel_attribute(primary_key).asc) - else - self - end - - relation = relation.offset(index) unless index.zero? - relation.limit(limit).to_a - end + def find_nth!(index) + find_nth(index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]") + end - def find_nth_from_last(index) - if loaded? - records[-index] - else + def find_nth_with_limit(index, limit) + # TODO: once the offset argument is removed from find_nth, + # find_nth_with_limit_and_offset can be merged into this method relation = if order_values.empty? && primary_key - order(arel_attribute(primary_key).asc) + order(arel_attribute(primary_key).asc) else self end - relation.to_a[-index] - # TODO: can be made more performant on large result sets by - # for instance, last(index)[-index] (which would require - # refactoring the last(n) finder method to make test suite pass), - # or by using a combination of reverse_order, limit, and offset, - # e.g., reverse_order.offset(index-1).first + relation = relation.offset(index) unless index.zero? + relation.limit(limit).to_a + end + + def find_nth_from_last(index) + if loaded? + records[-index] + else + relation = if order_values.empty? && primary_key + order(arel_attribute(primary_key).asc) + else + self + end + + relation.to_a[-index] + # TODO: can be made more performant on large result sets by + # for instance, last(index)[-index] (which would require + # refactoring the last(n) finder method to make test suite pass), + # or by using a combination of reverse_order, limit, and offset, + # e.g., reverse_order.offset(index-1).first + end end - end private - def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc: - if loaded? - records[index, limit] - else - index += offset - find_nth_with_limit(index, limit) + def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc: + if loaded? + records[index, limit] + else + index += offset + find_nth_with_limit(index, limit) + end end - end - def find_last(limit) - limit ? records.last(limit) : records.last - end + def find_last(limit) + limit ? records.last(limit) : records.last + end end end diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index 87a97803d5..df43b5c64b 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -83,85 +83,85 @@ module ActiveRecord private - def merge_preloads - return if other.preload_values.empty? && other.includes_values.empty? - - if other.klass == relation.klass - relation.preload!(*other.preload_values) unless other.preload_values.empty? - relation.includes!(other.includes_values) unless other.includes_values.empty? - else - reflection = relation.klass.reflect_on_all_associations.find do |r| - r.class_name == other.klass.name - end || return - - unless other.preload_values.empty? - relation.preload! reflection.name => other.preload_values - end + def merge_preloads + return if other.preload_values.empty? && other.includes_values.empty? + + if other.klass == relation.klass + relation.preload!(*other.preload_values) unless other.preload_values.empty? + relation.includes!(other.includes_values) unless other.includes_values.empty? + else + reflection = relation.klass.reflect_on_all_associations.find do |r| + r.class_name == other.klass.name + end || return - unless other.includes_values.empty? - relation.includes! reflection.name => other.includes_values + unless other.preload_values.empty? + relation.preload! reflection.name => other.preload_values + end + + unless other.includes_values.empty? + relation.includes! reflection.name => other.includes_values + end end end - end - def merge_joins - return if other.joins_values.blank? + def merge_joins + return if other.joins_values.blank? - if other.klass == relation.klass - relation.joins!(*other.joins_values) - else - joins_dependency, rest = other.joins_values.partition do |join| - case join - when Hash, Symbol, Array - true - else - false + if other.klass == relation.klass + relation.joins!(*other.joins_values) + else + joins_dependency, rest = other.joins_values.partition do |join| + case join + when Hash, Symbol, Array + true + else + false + end end - end - join_dependency = ActiveRecord::Associations::JoinDependency.new(other.klass, - joins_dependency, - []) - relation.joins! rest + join_dependency = ActiveRecord::Associations::JoinDependency.new(other.klass, + joins_dependency, + []) + relation.joins! rest - @relation = relation.joins join_dependency + @relation = relation.joins join_dependency + end end - end - def merge_multi_values - if other.reordering_value - # override any order specified in the original relation - relation.reorder! other.order_values - elsif other.order_values - # merge in order_values from relation - relation.order! other.order_values + def merge_multi_values + if other.reordering_value + # override any order specified in the original relation + relation.reorder! other.order_values + elsif other.order_values + # merge in order_values from relation + relation.order! other.order_values + end + + relation.extend(*other.extending_values) unless other.extending_values.blank? end - relation.extend(*other.extending_values) unless other.extending_values.blank? - end + def merge_single_values + if relation.from_clause.empty? + relation.from_clause = other.from_clause + end + relation.lock_value ||= other.lock_value - def merge_single_values - if relation.from_clause.empty? - relation.from_clause = other.from_clause + unless other.create_with_value.blank? + relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value) + end end - relation.lock_value ||= other.lock_value - unless other.create_with_value.blank? - relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value) + CLAUSE_METHOD_NAMES = CLAUSE_METHODS.map do |name| + ["#{name}_clause", "#{name}_clause="] end - end - - CLAUSE_METHOD_NAMES = CLAUSE_METHODS.map do |name| - ["#{name}_clause", "#{name}_clause="] - end - def merge_clauses - CLAUSE_METHOD_NAMES.each do |(reader, writer)| - clause = relation.send(reader) - other_clause = other.send(reader) - relation.send(writer, clause.merge(other_clause)) + def merge_clauses + CLAUSE_METHOD_NAMES.each do |(reader, writer)| + clause = relation.send(reader) + other_clause = other.send(reader) + relation.send(writer, clause.merge(other_clause)) + end end - end end end end diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 8dbff84950..df663a84ed 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -78,91 +78,91 @@ module ActiveRecord protected - attr_reader :table + attr_reader :table - def expand_from_hash(attributes) - return ["1=0"] if attributes.empty? + def expand_from_hash(attributes) + return ["1=0"] if attributes.empty? - attributes.flat_map do |key, value| - if value.is_a?(Hash) - associated_predicate_builder(key).expand_from_hash(value) - else - expand(key, value) + attributes.flat_map do |key, value| + if value.is_a?(Hash) + associated_predicate_builder(key).expand_from_hash(value) + else + expand(key, value) + end end end - end - def create_binds_for_hash(attributes) - result = attributes.dup - binds = [] - - attributes.each do |column_name, value| - case value - when Hash - attrs, bvs = associated_predicate_builder(column_name).create_binds_for_hash(value) - result[column_name] = attrs - binds += bvs - when Relation - binds += value.bound_attributes - when Range - first = value.begin - last = value.end - unless first.respond_to?(:infinite?) && first.infinite? - binds << build_bind_param(column_name, first) - first = Arel::Nodes::BindParam.new - end - unless last.respond_to?(:infinite?) && last.infinite? - binds << build_bind_param(column_name, last) - last = Arel::Nodes::BindParam.new - end - - result[column_name] = RangeHandler::RangeWithBinds.new(first, last, value.exclude_end?) - else - if can_be_bound?(column_name, value) - result[column_name] = Arel::Nodes::BindParam.new - binds << build_bind_param(column_name, value) + def create_binds_for_hash(attributes) + result = attributes.dup + binds = [] + + attributes.each do |column_name, value| + case value + when Hash + attrs, bvs = associated_predicate_builder(column_name).create_binds_for_hash(value) + result[column_name] = attrs + binds += bvs + when Relation + binds += value.bound_attributes + when Range + first = value.begin + last = value.end + unless first.respond_to?(:infinite?) && first.infinite? + binds << build_bind_param(column_name, first) + first = Arel::Nodes::BindParam.new + end + unless last.respond_to?(:infinite?) && last.infinite? + binds << build_bind_param(column_name, last) + last = Arel::Nodes::BindParam.new + end + + result[column_name] = RangeHandler::RangeWithBinds.new(first, last, value.exclude_end?) + else + if can_be_bound?(column_name, value) + result[column_name] = Arel::Nodes::BindParam.new + binds << build_bind_param(column_name, value) + end end end - end - [result, binds] - end + [result, binds] + end private - def associated_predicate_builder(association_name) - self.class.new(table.associated_table(association_name)) - end - - def convert_dot_notation_to_hash(attributes) - dot_notation = attributes.select do |k, v| - k.include?(".".freeze) && !v.is_a?(Hash) + def associated_predicate_builder(association_name) + self.class.new(table.associated_table(association_name)) end - dot_notation.each_key do |key| - table_name, column_name = key.split(".".freeze) - value = attributes.delete(key) - attributes[table_name] ||= {} + def convert_dot_notation_to_hash(attributes) + dot_notation = attributes.select do |k, v| + k.include?(".".freeze) && !v.is_a?(Hash) + end - attributes[table_name] = attributes[table_name].merge(column_name => value) - end + dot_notation.each_key do |key| + table_name, column_name = key.split(".".freeze) + value = attributes.delete(key) + attributes[table_name] ||= {} - attributes - end + attributes[table_name] = attributes[table_name].merge(column_name => value) + end - def handler_for(object) - @handlers.detect { |klass, _| klass === object }.last - end + attributes + end - def can_be_bound?(column_name, value) - !value.nil? && - handler_for(value).is_a?(BasicObjectHandler) && - !table.associated_with?(column_name) - end + def handler_for(object) + @handlers.detect { |klass, _| klass === object }.last + end - def build_bind_param(column_name, value) - Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name)) - end + def can_be_bound?(column_name, value) + !value.nil? && + handler_for(value).is_a?(BasicObjectHandler) && + !table.associated_with?(column_name) + end + + def build_bind_param(column_name, value) + Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name)) + end end end diff --git a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb index 95dbd6a77f..6400caba06 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb @@ -31,13 +31,13 @@ module ActiveRecord protected - attr_reader :predicate_builder + attr_reader :predicate_builder - module NullPredicate # :nodoc: - def self.or(other) - other + module NullPredicate # :nodoc: + def self.or(other) + other + end end - end end end end diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb index 413cb9fd84..7e20cb2c63 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb @@ -30,7 +30,7 @@ module ActiveRecord protected - attr_reader :predicate_builder + attr_reader :predicate_builder end class AssociationQueryValue # :nodoc: @@ -60,30 +60,30 @@ module ActiveRecord private - def primary_key - associated_table.association_primary_key(base_class) - end + def primary_key + associated_table.association_primary_key(base_class) + end - def polymorphic_base_class_from_value - case value - when Relation - value.klass.base_class - when Array - val = value.compact.first - val.class.base_class if val.is_a?(Base) - when Base - value.class.base_class + def polymorphic_base_class_from_value + case value + when Relation + value.klass.base_class + when Array + val = value.compact.first + val.class.base_class if val.is_a?(Base) + when Base + value.class.base_class + end end - end - def convert_to_id(value) - case value - when Base - value._read_attribute(primary_key) - else - value + def convert_to_id(value) + case value + when Base + value._read_attribute(primary_key) + else + value + end end - end end end end diff --git a/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb index 6fa5b16f73..65c5159704 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb @@ -11,7 +11,7 @@ module ActiveRecord protected - attr_reader :predicate_builder + attr_reader :predicate_builder end end end diff --git a/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb index ed313fc9d4..73ad864765 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb @@ -12,16 +12,16 @@ module ActiveRecord protected - attr_reader :predicate_builder + attr_reader :predicate_builder private - def print_deprecation_warning - ActiveSupport::Deprecation.warn(<<-MSG.squish) + def print_deprecation_warning + ActiveSupport::Deprecation.warn(<<-MSG.squish) Passing a class as a value in an Active Record query is deprecated and will be removed. Pass a string instead. MSG - end + end end end end diff --git a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb index b6c6240343..0c7f92b3d0 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb @@ -23,7 +23,7 @@ module ActiveRecord protected - attr_reader :predicate_builder + attr_reader :predicate_builder end class PolymorphicArrayValue # :nodoc: @@ -41,17 +41,17 @@ module ActiveRecord private - def primary_key(value) - associated_table.association_primary_key(base_class(value)) - end + def primary_key(value) + associated_table.association_primary_key(base_class(value)) + end - def base_class(value) - value.class.base_class - end + def base_class(value) + value.class.base_class + end - def convert_to_id(value) - value._read_attribute(primary_key(value)) - end + def convert_to_id(value) + value._read_attribute(primary_key(value)) + end end end end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 3b86f1c605..619b947f57 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -952,241 +952,241 @@ module ActiveRecord private - def assert_mutability! - raise ImmutableRelation if @loaded - raise ImmutableRelation if defined?(@arel) && @arel - end + def assert_mutability! + raise ImmutableRelation if @loaded + raise ImmutableRelation if defined?(@arel) && @arel + end - def build_arel - arel = Arel::SelectManager.new(table) + def build_arel + arel = Arel::SelectManager.new(table) - build_joins(arel, joins_values.flatten) unless joins_values.empty? - build_left_outer_joins(arel, left_outer_joins_values.flatten) unless left_outer_joins_values.empty? + build_joins(arel, joins_values.flatten) unless joins_values.empty? + build_left_outer_joins(arel, left_outer_joins_values.flatten) unless left_outer_joins_values.empty? - arel.where(where_clause.ast) unless where_clause.empty? - arel.having(having_clause.ast) unless having_clause.empty? - if limit_value - if string_containing_comma?(limit_value) - arel.take(connection.sanitize_limit(limit_value)) - else - arel.take(Arel::Nodes::BindParam.new) + arel.where(where_clause.ast) unless where_clause.empty? + arel.having(having_clause.ast) unless having_clause.empty? + if limit_value + if string_containing_comma?(limit_value) + arel.take(connection.sanitize_limit(limit_value)) + else + arel.take(Arel::Nodes::BindParam.new) + end end - end - arel.skip(Arel::Nodes::BindParam.new) if offset_value - arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty? + arel.skip(Arel::Nodes::BindParam.new) if offset_value + arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty? - build_order(arel) + build_order(arel) - build_select(arel) + build_select(arel) - arel.distinct(distinct_value) - arel.from(build_from) unless from_clause.empty? - arel.lock(lock_value) if lock_value + arel.distinct(distinct_value) + arel.from(build_from) unless from_clause.empty? + arel.lock(lock_value) if lock_value - arel - end - - def symbol_unscoping(scope) - if !VALID_UNSCOPING_VALUES.include?(scope) - raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}." + arel end - clause_method = Relation::CLAUSE_METHODS.include?(scope) - multi_val_method = Relation::MULTI_VALUE_METHODS.include?(scope) - if clause_method - unscope_code = "#{scope}_clause=" - else - unscope_code = "#{scope}_value#{'s' if multi_val_method}=" - end + def symbol_unscoping(scope) + if !VALID_UNSCOPING_VALUES.include?(scope) + raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}." + end - case scope - when :order - result = [] - else - result = [] if multi_val_method - end + clause_method = Relation::CLAUSE_METHODS.include?(scope) + multi_val_method = Relation::MULTI_VALUE_METHODS.include?(scope) + if clause_method + unscope_code = "#{scope}_clause=" + else + unscope_code = "#{scope}_value#{'s' if multi_val_method}=" + end - self.send(unscope_code, result) - end + case scope + when :order + result = [] + else + result = [] if multi_val_method + end - def build_from - opts = from_clause.value - name = from_clause.name - case opts - when Relation - name ||= "subquery" - opts.arel.as(name.to_s) - else - opts + self.send(unscope_code, result) end - end - def build_left_outer_joins(manager, outer_joins) - buckets = outer_joins.group_by do |join| - case join - when Hash, Symbol, Array - :association_join + def build_from + opts = from_clause.value + name = from_clause.name + case opts + when Relation + name ||= "subquery" + opts.arel.as(name.to_s) else - raise ArgumentError, "only Hash, Symbol and Array are allowed" + opts end end - build_join_query(manager, buckets, Arel::Nodes::OuterJoin) - end - - def build_joins(manager, joins) - buckets = joins.group_by do |join| - case join - when String - :string_join - when Hash, Symbol, Array - :association_join - when ActiveRecord::Associations::JoinDependency - :stashed_join - when Arel::Nodes::Join - :join_node - else - raise "unknown class: %s" % join.class.name + def build_left_outer_joins(manager, outer_joins) + buckets = outer_joins.group_by do |join| + case join + when Hash, Symbol, Array + :association_join + else + raise ArgumentError, "only Hash, Symbol and Array are allowed" + end end + + build_join_query(manager, buckets, Arel::Nodes::OuterJoin) end - build_join_query(manager, buckets, Arel::Nodes::InnerJoin) - end + def build_joins(manager, joins) + buckets = joins.group_by do |join| + case join + when String + :string_join + when Hash, Symbol, Array + :association_join + when ActiveRecord::Associations::JoinDependency + :stashed_join + when Arel::Nodes::Join + :join_node + else + raise "unknown class: %s" % join.class.name + end + end - def build_join_query(manager, buckets, join_type) - buckets.default = [] + build_join_query(manager, buckets, Arel::Nodes::InnerJoin) + end - association_joins = buckets[:association_join] - stashed_association_joins = buckets[:stashed_join] - join_nodes = buckets[:join_node].uniq - string_joins = buckets[:string_join].map(&:strip).uniq + def build_join_query(manager, buckets, join_type) + buckets.default = [] - join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins) + association_joins = buckets[:association_join] + stashed_association_joins = buckets[:stashed_join] + join_nodes = buckets[:join_node].uniq + string_joins = buckets[:string_join].map(&:strip).uniq - join_dependency = ActiveRecord::Associations::JoinDependency.new( - @klass, - association_joins, - join_list - ) + join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins) - join_infos = join_dependency.join_constraints stashed_association_joins, join_type + join_dependency = ActiveRecord::Associations::JoinDependency.new( + @klass, + association_joins, + join_list + ) - join_infos.each do |info| - info.joins.each { |join| manager.from(join) } - manager.bind_values.concat info.binds - end + join_infos = join_dependency.join_constraints stashed_association_joins, join_type - manager.join_sources.concat(join_list) + join_infos.each do |info| + info.joins.each { |join| manager.from(join) } + manager.bind_values.concat info.binds + end - manager - end + manager.join_sources.concat(join_list) - def convert_join_strings_to_ast(table, joins) - joins - .flatten - .reject(&:blank?) - .map { |join| table.create_string_join(Arel.sql(join)) } - end + manager + end - def build_select(arel) - if select_values.any? - arel.project(*arel_columns(select_values.uniq)) - else - arel.project(@klass.arel_table[Arel.star]) + def convert_join_strings_to_ast(table, joins) + joins + .flatten + .reject(&:blank?) + .map { |join| table.create_string_join(Arel.sql(join)) } end - end - def arel_columns(columns) - columns.map do |field| - if (Symbol === field || String === field) && (klass.has_attribute?(field) || klass.attribute_alias?(field)) && !from_clause.value - arel_attribute(field) - elsif Symbol === field - connection.quote_table_name(field.to_s) + def build_select(arel) + if select_values.any? + arel.project(*arel_columns(select_values.uniq)) else - field + arel.project(@klass.arel_table[Arel.star]) end end - end - def reverse_sql_order(order_query) - if order_query.empty? - return [arel_attribute(primary_key).desc] if primary_key - raise IrreversibleOrderError, - "Relation has no current order and table has no primary key to be used as default order" + def arel_columns(columns) + columns.map do |field| + if (Symbol === field || String === field) && (klass.has_attribute?(field) || klass.attribute_alias?(field)) && !from_clause.value + arel_attribute(field) + elsif Symbol === field + connection.quote_table_name(field.to_s) + else + field + end + end end - order_query.flat_map do |o| - case o - when Arel::Attribute - o.desc - when Arel::Nodes::Ordering - o.reverse - when String - if does_not_support_reverse?(o) - raise IrreversibleOrderError, "Order #{o.inspect} can not be reversed automatically" - end - o.split(",").map! do |s| - s.strip! - s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || s.concat(" DESC") + def reverse_sql_order(order_query) + if order_query.empty? + return [arel_attribute(primary_key).desc] if primary_key + raise IrreversibleOrderError, + "Relation has no current order and table has no primary key to be used as default order" + end + + order_query.flat_map do |o| + case o + when Arel::Attribute + o.desc + when Arel::Nodes::Ordering + o.reverse + when String + if does_not_support_reverse?(o) + raise IrreversibleOrderError, "Order #{o.inspect} can not be reversed automatically" + end + o.split(",").map! do |s| + s.strip! + s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || s.concat(" DESC") + end + else + o end - else - o end end - end - def does_not_support_reverse?(order) - # Uses SQL function with multiple arguments. - /\([^()]*,[^()]*\)/.match?(order) || - # Uses "nulls first" like construction. - /nulls (first|last)\Z/i.match?(order) - end + def does_not_support_reverse?(order) + # Uses SQL function with multiple arguments. + /\([^()]*,[^()]*\)/.match?(order) || + # Uses "nulls first" like construction. + /nulls (first|last)\Z/i.match?(order) + end - def build_order(arel) - orders = order_values.uniq - orders.reject!(&:blank?) + def build_order(arel) + orders = order_values.uniq + orders.reject!(&:blank?) - arel.order(*orders) unless orders.empty? - end + arel.order(*orders) unless orders.empty? + end - VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC, - "asc", "desc", "ASC", "DESC"] # :nodoc: + VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC, + "asc", "desc", "ASC", "DESC"] # :nodoc: - def validate_order_args(args) - args.each do |arg| - next unless arg.is_a?(Hash) - arg.each do |_key, value| - raise ArgumentError, "Direction \"#{value}\" is invalid. Valid " \ - "directions are: #{VALID_DIRECTIONS.inspect}" unless VALID_DIRECTIONS.include?(value) + def validate_order_args(args) + args.each do |arg| + next unless arg.is_a?(Hash) + arg.each do |_key, value| + raise ArgumentError, "Direction \"#{value}\" is invalid. Valid " \ + "directions are: #{VALID_DIRECTIONS.inspect}" unless VALID_DIRECTIONS.include?(value) + end end end - end - - def preprocess_order_args(order_args) - order_args.map! do |arg| - klass.send(:sanitize_sql_for_order, arg) - end - order_args.flatten! - validate_order_args(order_args) - references = order_args.grep(String) - references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact! - references!(references) if references.any? - - # if a symbol is given we prepend the quoted table name - order_args.map! do |arg| - case arg - when Symbol - arel_attribute(arg).asc - when Hash - arg.map { |field, dir| - arel_attribute(field).send(dir.downcase) - } - else - arg + def preprocess_order_args(order_args) + order_args.map! do |arg| + klass.send(:sanitize_sql_for_order, arg) end - end.flatten! - end + order_args.flatten! + validate_order_args(order_args) + + references = order_args.grep(String) + references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact! + references!(references) if references.any? + + # if a symbol is given we prepend the quoted table name + order_args.map! do |arg| + case arg + when Symbol + arel_attribute(arg).asc + when Hash + arg.map { |field, dir| + arel_attribute(field).send(dir.downcase) + } + else + arg + end + end.flatten! + end # Checks to make sure that the arguments are not blank. Note that if some # blank-like object were initially passed into the query method, then this @@ -1204,34 +1204,34 @@ module ActiveRecord # check_if_method_has_arguments!("references", args) # ... # end - def check_if_method_has_arguments!(method_name, args) - if args.blank? - raise ArgumentError, "The method .#{method_name}() must contain arguments." + def check_if_method_has_arguments!(method_name, args) + if args.blank? + raise ArgumentError, "The method .#{method_name}() must contain arguments." + end end - end - def structurally_incompatible_values_for_or(other) - Relation::SINGLE_VALUE_METHODS.reject { |m| send("#{m}_value") == other.send("#{m}_value") } + - (Relation::MULTI_VALUE_METHODS - [:extending]).reject { |m| send("#{m}_values") == other.send("#{m}_values") } + - (Relation::CLAUSE_METHODS - [:having, :where]).reject { |m| send("#{m}_clause") == other.send("#{m}_clause") } - end + def structurally_incompatible_values_for_or(other) + Relation::SINGLE_VALUE_METHODS.reject { |m| send("#{m}_value") == other.send("#{m}_value") } + + (Relation::MULTI_VALUE_METHODS - [:extending]).reject { |m| send("#{m}_values") == other.send("#{m}_values") } + + (Relation::CLAUSE_METHODS - [:having, :where]).reject { |m| send("#{m}_clause") == other.send("#{m}_clause") } + end - def new_where_clause - Relation::WhereClause.empty - end - alias new_having_clause new_where_clause + def new_where_clause + Relation::WhereClause.empty + end + alias new_having_clause new_where_clause - def where_clause_factory - @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder) - end - alias having_clause_factory where_clause_factory + def where_clause_factory + @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder) + end + alias having_clause_factory where_clause_factory - def new_from_clause - Relation::FromClause.empty - end + def new_from_clause + Relation::FromClause.empty + end - def string_containing_comma?(value) - ::String === value && value.include?(",") - end + def string_containing_comma?(value) + ::String === value && value.include?(",") + end end end diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb index e1376ce00a..402f8acfd1 100644 --- a/activerecord/lib/active_record/relation/where_clause.rb +++ b/activerecord/lib/active_record/relation/where_clause.rb @@ -86,89 +86,89 @@ module ActiveRecord protected - attr_reader :predicates + attr_reader :predicates - def referenced_columns - @referenced_columns ||= begin - equality_nodes = predicates.select { |n| equality_node?(n) } - Set.new(equality_nodes, &:left) + def referenced_columns + @referenced_columns ||= begin + equality_nodes = predicates.select { |n| equality_node?(n) } + Set.new(equality_nodes, &:left) + end end - end private - def predicates_unreferenced_by(other) - predicates.reject do |n| - equality_node?(n) && other.referenced_columns.include?(n.left) + def predicates_unreferenced_by(other) + predicates.reject do |n| + equality_node?(n) && other.referenced_columns.include?(n.left) + end end - end - def equality_node?(node) - node.respond_to?(:operator) && node.operator == :== - end - - def non_conflicting_binds(other) - conflicts = referenced_columns & other.referenced_columns - conflicts.map! { |node| node.name.to_s } - binds.reject { |attr| conflicts.include?(attr.name) } - end + def equality_node?(node) + node.respond_to?(:operator) && node.operator == :== + end - def inverted_predicates - predicates.map { |node| invert_predicate(node) } - end + def non_conflicting_binds(other) + conflicts = referenced_columns & other.referenced_columns + conflicts.map! { |node| node.name.to_s } + binds.reject { |attr| conflicts.include?(attr.name) } + end - def invert_predicate(node) - case node - when NilClass - raise ArgumentError, "Invalid argument for .where.not(), got nil." - when Arel::Nodes::In - Arel::Nodes::NotIn.new(node.left, node.right) - when Arel::Nodes::Equality - Arel::Nodes::NotEqual.new(node.left, node.right) - when String - Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(node)) - else - Arel::Nodes::Not.new(node) + def inverted_predicates + predicates.map { |node| invert_predicate(node) } end - end - def predicates_except(columns) - predicates.reject do |node| + def invert_predicate(node) case node - when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual - subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right) - columns.include?(subrelation.name.to_s) + when NilClass + raise ArgumentError, "Invalid argument for .where.not(), got nil." + when Arel::Nodes::In + Arel::Nodes::NotIn.new(node.left, node.right) + when Arel::Nodes::Equality + Arel::Nodes::NotEqual.new(node.left, node.right) + when String + Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(node)) + else + Arel::Nodes::Not.new(node) end end - end - def binds_except(columns) - binds.reject do |attr| - columns.include?(attr.name) + def predicates_except(columns) + predicates.reject do |node| + case node + when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual + subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right) + columns.include?(subrelation.name.to_s) + end + end end - end - def predicates_with_wrapped_sql_literals - non_empty_predicates.map do |node| - if Arel::Nodes::Equality === node - node - else - wrap_sql_literal(node) + def binds_except(columns) + binds.reject do |attr| + columns.include?(attr.name) end end - end - ARRAY_WITH_EMPTY_STRING = [""] - def non_empty_predicates - predicates - ARRAY_WITH_EMPTY_STRING - end + def predicates_with_wrapped_sql_literals + non_empty_predicates.map do |node| + if Arel::Nodes::Equality === node + node + else + wrap_sql_literal(node) + end + end + end - def wrap_sql_literal(node) - if ::String === node - node = Arel.sql(node) + ARRAY_WITH_EMPTY_STRING = [""] + def non_empty_predicates + predicates - ARRAY_WITH_EMPTY_STRING + end + + def wrap_sql_literal(node) + if ::String === node + node = Arel.sql(node) + end + Arel::Nodes::Grouping.new(node) end - Arel::Nodes::Grouping.new(node) - end end end end diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb index dbf172a577..038ebbfe21 100644 --- a/activerecord/lib/active_record/relation/where_clause_factory.rb +++ b/activerecord/lib/active_record/relation/where_clause_factory.rb @@ -32,7 +32,7 @@ module ActiveRecord protected - attr_reader :klass, :predicate_builder + attr_reader :klass, :predicate_builder end end end diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb index b9fd0f5326..f5383f4c14 100644 --- a/activerecord/lib/active_record/result.rb +++ b/activerecord/lib/active_record/result.rb @@ -103,36 +103,36 @@ module ActiveRecord private - def column_type(name, type_overrides = {}) - type_overrides.fetch(name) do - column_types.fetch(name, IDENTITY_TYPE) + def column_type(name, type_overrides = {}) + type_overrides.fetch(name) do + column_types.fetch(name, IDENTITY_TYPE) + end end - end - def hash_rows - @hash_rows ||= - begin - # We freeze the strings to prevent them getting duped when - # used as keys in ActiveRecord::Base's @attributes hash - columns = @columns.map { |c| c.dup.freeze } - @rows.map { |row| - # In the past we used Hash[columns.zip(row)] - # though elegant, the verbose way is much more efficient - # both time and memory wise cause it avoids a big array allocation - # this method is called a lot and needs to be micro optimised - hash = {} - - index = 0 - length = columns.length - - while index < length - hash[columns[index]] = row[index] - index += 1 - end - - hash - } - end - end + def hash_rows + @hash_rows ||= + begin + # We freeze the strings to prevent them getting duped when + # used as keys in ActiveRecord::Base's @attributes hash + columns = @columns.map { |c| c.dup.freeze } + @rows.map { |row| + # In the past we used Hash[columns.zip(row)] + # though elegant, the verbose way is much more efficient + # both time and memory wise cause it avoids a big array allocation + # this method is called a lot and needs to be micro optimised + hash = {} + + index = 0 + length = columns.length + + while index < length + hash[columns[index]] = row[index] + index += 1 + end + + hash + } + end + end end end diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 26679bb1c3..6617008344 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -28,16 +28,16 @@ module ActiveRecord # # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'") # # => "name='foo''bar' and group_id='4'" - def sanitize_sql_for_conditions(condition) - return nil if condition.blank? + def sanitize_sql_for_conditions(condition) + return nil if condition.blank? - case condition - when Array; sanitize_sql_array(condition) - else condition + case condition + when Array; sanitize_sql_array(condition) + else condition + end end - end - alias_method :sanitize_sql, :sanitize_sql_for_conditions - alias_method :sanitize_conditions, :sanitize_sql + alias_method :sanitize_sql, :sanitize_sql_for_conditions + alias_method :sanitize_conditions, :sanitize_sql # Accepts an array, hash, or string of SQL conditions and sanitizes # them into a valid SQL fragment for a SET clause. @@ -53,13 +53,13 @@ module ActiveRecord # # sanitize_sql_for_assignment("name=NULL and group_id='4'") # # => "name=NULL and group_id='4'" - def sanitize_sql_for_assignment(assignments, default_table_name = self.table_name) - case assignments - when Array; sanitize_sql_array(assignments) - when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name) - else assignments + def sanitize_sql_for_assignment(assignments, default_table_name = self.table_name) + case assignments + when Array; sanitize_sql_array(assignments) + when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name) + else assignments + end end - end # Accepts an array, or string of SQL conditions and sanitizes # them into a valid SQL fragment for an ORDER clause. @@ -69,13 +69,13 @@ module ActiveRecord # # sanitize_sql_for_order("id ASC") # # => "id ASC" - def sanitize_sql_for_order(condition) - if condition.is_a?(Array) && condition.first.to_s.include?("?") - sanitize_sql_array(condition) - else - condition + def sanitize_sql_for_order(condition) + if condition.is_a?(Array) && condition.first.to_s.include?("?") + sanitize_sql_array(condition) + else + condition + end end - end # Accepts a hash of SQL conditions and replaces those attributes # that correspond to a {#composed_of}[rdoc-ref:Aggregations::ClassMethods#composed_of] @@ -92,36 +92,36 @@ module ActiveRecord # # { address: Address.new("813 abc st.", "chicago") } # # => { address_street: "813 abc st.", address_city: "chicago" } - def expand_hash_conditions_for_aggregates(attrs) - expanded_attrs = {} - attrs.each do |attr, value| - if aggregation = reflect_on_aggregation(attr.to_sym) - mapping = aggregation.mapping - mapping.each do |field_attr, aggregate_attr| - if mapping.size == 1 && !value.respond_to?(aggregate_attr) - expanded_attrs[field_attr] = value - else - expanded_attrs[field_attr] = value.send(aggregate_attr) + def expand_hash_conditions_for_aggregates(attrs) + expanded_attrs = {} + attrs.each do |attr, value| + if aggregation = reflect_on_aggregation(attr.to_sym) + mapping = aggregation.mapping + mapping.each do |field_attr, aggregate_attr| + if mapping.size == 1 && !value.respond_to?(aggregate_attr) + expanded_attrs[field_attr] = value + else + expanded_attrs[field_attr] = value.send(aggregate_attr) + end end + else + expanded_attrs[attr] = value end - else - expanded_attrs[attr] = value end + expanded_attrs end - expanded_attrs - end # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause. # # sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts") # # => "`posts`.`status` = NULL, `posts`.`group_id` = 1" - def sanitize_sql_hash_for_assignment(attrs, table) - c = connection - attrs.map do |attr, value| - value = type_for_attribute(attr.to_s).serialize(value) - "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}" - end.join(", ") - end + def sanitize_sql_hash_for_assignment(attrs, table) + c = connection + attrs.map do |attr, value| + value = type_for_attribute(attr.to_s).serialize(value) + "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}" + end.join(", ") + end # Sanitizes a +string+ so that it is safe to use within an SQL # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%". @@ -137,10 +137,10 @@ module ActiveRecord # # sanitize_sql_like("snake_cased_string", "!") # # => "snake!_cased!_string" - def sanitize_sql_like(string, escape_character = "\\") - pattern = Regexp.union(escape_character, "%", "_") - string.gsub(pattern) { |x| [escape_character, x].join } - end + def sanitize_sql_like(string, escape_character = "\\") + pattern = Regexp.union(escape_character, "%", "_") + string.gsub(pattern) { |x| [escape_character, x].join } + end # Accepts an array of conditions. The array has each value # sanitized and interpolated into the SQL statement. @@ -153,65 +153,65 @@ module ActiveRecord # # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4]) # # => "name='foo''bar' and group_id='4'" - def sanitize_sql_array(ary) - statement, *values = ary - if values.first.is_a?(Hash) && /:\w+/.match?(statement) - replace_named_bind_variables(statement, values.first) - elsif statement.include?("?") - replace_bind_variables(statement, values) - elsif statement.blank? - statement - else - statement % values.collect { |value| connection.quote_string(value.to_s) } + def sanitize_sql_array(ary) + statement, *values = ary + if values.first.is_a?(Hash) && /:\w+/.match?(statement) + replace_named_bind_variables(statement, values.first) + elsif statement.include?("?") + replace_bind_variables(statement, values) + elsif statement.blank? + statement + else + statement % values.collect { |value| connection.quote_string(value.to_s) } + end end - end - def replace_bind_variables(statement, values) # :nodoc: - raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size) - bound = values.dup - c = connection - statement.gsub(/\?/) do - replace_bind_variable(bound.shift, c) + def replace_bind_variables(statement, values) # :nodoc: + raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size) + bound = values.dup + c = connection + statement.gsub(/\?/) do + replace_bind_variable(bound.shift, c) + end end - end - def replace_bind_variable(value, c = connection) # :nodoc: - if ActiveRecord::Relation === value - value.to_sql - else - quote_bound_value(value, c) + def replace_bind_variable(value, c = connection) # :nodoc: + if ActiveRecord::Relation === value + value.to_sql + else + quote_bound_value(value, c) + end end - end - def replace_named_bind_variables(statement, bind_vars) # :nodoc: - statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match| - if $1 == ":" # skip postgresql casts - match # return the whole match - elsif bind_vars.include?(match = $2.to_sym) - replace_bind_variable(bind_vars[match]) - else - raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}" + def replace_named_bind_variables(statement, bind_vars) # :nodoc: + statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match| + if $1 == ":" # skip postgresql casts + match # return the whole match + elsif bind_vars.include?(match = $2.to_sym) + replace_bind_variable(bind_vars[match]) + else + raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}" + end end end - end - def quote_bound_value(value, c = connection) # :nodoc: - if value.respond_to?(:map) && !value.acts_like?(:string) - if value.respond_to?(:empty?) && value.empty? - c.quote(nil) + def quote_bound_value(value, c = connection) # :nodoc: + if value.respond_to?(:map) && !value.acts_like?(:string) + if value.respond_to?(:empty?) && value.empty? + c.quote(nil) + else + value.map { |v| c.quote(v) }.join(",") + end else - value.map { |v| c.quote(v) }.join(",") + c.quote(value) end - else - c.quote(value) end - end - def raise_if_bind_arity_mismatch(statement, expected, provided) # :nodoc: - unless expected == provided - raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}" + def raise_if_bind_arity_mismatch(statement, expected, provided) # :nodoc: + unless expected == provided + raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}" + end end - end end # TODO: Deprecate this diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb index 78231594d4..d1bd1cd89a 100644 --- a/activerecord/lib/active_record/scoping.rb +++ b/activerecord/lib/active_record/scoping.rb @@ -94,11 +94,11 @@ module ActiveRecord private - def raise_invalid_scope_type!(scope_type) - if !VALID_SCOPE_TYPES.include?(scope_type) - raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES" + def raise_invalid_scope_type!(scope_type) + if !VALID_SCOPE_TYPES.include?(scope_type) + raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES" + end end - end end end end diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index 9eab59ac78..572a45aac9 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -87,62 +87,62 @@ module ActiveRecord # # Should return a scope, you can call 'super' here etc. # end # end - def default_scope(scope = nil) - scope = Proc.new if block_given? - - if scope.is_a?(Relation) || !scope.respond_to?(:call) - raise ArgumentError, - "Support for calling #default_scope without a block is removed. For example instead " \ - "of `default_scope where(color: 'red')`, please use " \ - "`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \ - "self.default_scope.)" - end + def default_scope(scope = nil) + scope = Proc.new if block_given? + + if scope.is_a?(Relation) || !scope.respond_to?(:call) + raise ArgumentError, + "Support for calling #default_scope without a block is removed. For example instead " \ + "of `default_scope where(color: 'red')`, please use " \ + "`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \ + "self.default_scope.)" + end - self.default_scopes += [scope] - end + self.default_scopes += [scope] + end - def build_default_scope(base_rel = nil) # :nodoc: - return if abstract_class? + def build_default_scope(base_rel = nil) # :nodoc: + return if abstract_class? - if self.default_scope_override.nil? - self.default_scope_override = !Base.is_a?(method(:default_scope).owner) - end + if self.default_scope_override.nil? + self.default_scope_override = !Base.is_a?(method(:default_scope).owner) + end - if self.default_scope_override - # The user has defined their own default scope method, so call that - evaluate_default_scope { default_scope } - elsif default_scopes.any? - base_rel ||= relation - evaluate_default_scope do - default_scopes.inject(base_rel) do |default_scope, scope| - scope = scope.respond_to?(:to_proc) ? scope : scope.method(:call) - default_scope.merge(base_rel.instance_exec(&scope)) + if self.default_scope_override + # The user has defined their own default scope method, so call that + evaluate_default_scope { default_scope } + elsif default_scopes.any? + base_rel ||= relation + evaluate_default_scope do + default_scopes.inject(base_rel) do |default_scope, scope| + scope = scope.respond_to?(:to_proc) ? scope : scope.method(:call) + default_scope.merge(base_rel.instance_exec(&scope)) + end end end end - end - def ignore_default_scope? # :nodoc: - ScopeRegistry.value_for(:ignore_default_scope, base_class) - end + def ignore_default_scope? # :nodoc: + ScopeRegistry.value_for(:ignore_default_scope, base_class) + end - def ignore_default_scope=(ignore) # :nodoc: - ScopeRegistry.set_value_for(:ignore_default_scope, base_class, ignore) - end + def ignore_default_scope=(ignore) # :nodoc: + ScopeRegistry.set_value_for(:ignore_default_scope, base_class, ignore) + end # The ignore_default_scope flag is used to prevent an infinite recursion # situation where a default scope references a scope which has a default # scope which references a scope... - def evaluate_default_scope # :nodoc: - return if ignore_default_scope? - - begin - self.ignore_default_scope = true - yield - ensure - self.ignore_default_scope = false + def evaluate_default_scope # :nodoc: + return if ignore_default_scope? + + begin + self.ignore_default_scope = true + yield + ensure + self.ignore_default_scope = false + end end - end end end end diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb index 2169d64137..f6bf2c7e48 100644 --- a/activerecord/lib/active_record/store.rb +++ b/activerecord/lib/active_record/store.rb @@ -177,34 +177,34 @@ module ActiveRecord end end - class IndifferentCoder # :nodoc: - def initialize(coder_or_class_name) - @coder = - if coder_or_class_name.respond_to?(:load) && coder_or_class_name.respond_to?(:dump) - coder_or_class_name - else - ActiveRecord::Coders::YAMLColumn.new(coder_or_class_name || Object) - end - end + class IndifferentCoder # :nodoc: + def initialize(coder_or_class_name) + @coder = + if coder_or_class_name.respond_to?(:load) && coder_or_class_name.respond_to?(:dump) + coder_or_class_name + else + ActiveRecord::Coders::YAMLColumn.new(coder_or_class_name || Object) + end + end - def dump(obj) - @coder.dump self.class.as_indifferent_hash(obj) - end + def dump(obj) + @coder.dump self.class.as_indifferent_hash(obj) + end - def load(yaml) - self.class.as_indifferent_hash(@coder.load(yaml || "")) - end + def load(yaml) + self.class.as_indifferent_hash(@coder.load(yaml || "")) + end - def self.as_indifferent_hash(obj) - case obj - when ActiveSupport::HashWithIndifferentAccess - obj - when Hash - obj.with_indifferent_access - else - ActiveSupport::HashWithIndifferentAccess.new + def self.as_indifferent_hash(obj) + case obj + when ActiveSupport::HashWithIndifferentAccess + obj + when Hash + obj.with_indifferent_access + else + ActiveSupport::HashWithIndifferentAccess.new + end end end - end end end diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb index 5fe0d8b5e4..e8d6a144f9 100644 --- a/activerecord/lib/active_record/table_metadata.rb +++ b/activerecord/lib/active_record/table_metadata.rb @@ -64,6 +64,6 @@ module ActiveRecord protected - attr_reader :klass, :arel_table, :association + attr_reader :klass, :arel_table, :association end end diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 269a1488f5..c0764fc4ab 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -77,7 +77,7 @@ module ActiveRecord def fixtures_path @fixtures_path ||= if ENV["FIXTURES_PATH"] - File.join(root, ENV["FIXTURES_PATH"]) + File.join(root, ENV["FIXTURES_PATH"]) else File.join(root, "test", "fixtures") end @@ -275,39 +275,39 @@ module ActiveRecord private - def class_for_adapter(adapter) - key = @tasks.keys.detect { |pattern| adapter[pattern] } - unless key - raise DatabaseNotSupported, "Rake tasks not supported by '#{adapter}' adapter" + def class_for_adapter(adapter) + key = @tasks.keys.detect { |pattern| adapter[pattern] } + unless key + raise DatabaseNotSupported, "Rake tasks not supported by '#{adapter}' adapter" + end + @tasks[key] end - @tasks[key] - end - def each_current_configuration(environment) - environments = [environment] - environments << "test" if environment == "development" + def each_current_configuration(environment) + environments = [environment] + environments << "test" if environment == "development" - configurations = ActiveRecord::Base.configurations.values_at(*environments) - configurations.compact.each do |configuration| - yield configuration unless configuration["database"].blank? + configurations = ActiveRecord::Base.configurations.values_at(*environments) + configurations.compact.each do |configuration| + yield configuration unless configuration["database"].blank? + end end - end - def each_local_configuration - ActiveRecord::Base.configurations.each_value do |configuration| - next unless configuration["database"] + def each_local_configuration + ActiveRecord::Base.configurations.each_value do |configuration| + next unless configuration["database"] - if local_database?(configuration) - yield configuration - else - $stderr.puts "This task only modifies local databases. #{configuration['database']} is on a remote host." + if local_database?(configuration) + yield configuration + else + $stderr.puts "This task only modifies local databases. #{configuration['database']} is on a remote host." + end end end - end - def local_database?(configuration) - configuration["host"].blank? || LOCAL_HOSTS.include?(configuration["host"]) - end + def local_database?(configuration) + configuration["host"].blank? || LOCAL_HOSTS.include?(configuration["host"]) + end end end end diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb index d10c1b05cd..c8b89f1fdf 100644 --- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -74,79 +74,79 @@ module ActiveRecord private - def configuration - @configuration - end + def configuration + @configuration + end - def configuration_without_database - configuration.merge("database" => nil) - end + def configuration_without_database + configuration.merge("database" => nil) + end - def creation_options - Hash.new.tap do |options| - options[:charset] = configuration["encoding"] if configuration.include? "encoding" - options[:collation] = configuration["collation"] if configuration.include? "collation" + def creation_options + Hash.new.tap do |options| + options[:charset] = configuration["encoding"] if configuration.include? "encoding" + options[:collation] = configuration["collation"] if configuration.include? "collation" + end end - end - def error_class - if configuration["adapter"].include?("jdbc") - require "active_record/railties/jdbcmysql_error" - ArJdbcMySQL::Error - elsif defined?(Mysql2) - Mysql2::Error - else - StandardError + def error_class + if configuration["adapter"].include?("jdbc") + require "active_record/railties/jdbcmysql_error" + ArJdbcMySQL::Error + elsif defined?(Mysql2) + Mysql2::Error + else + StandardError + end end - end - def grant_statement - <<-SQL + def grant_statement + <<-SQL GRANT ALL PRIVILEGES ON #{configuration['database']}.* TO '#{configuration['username']}'@'localhost' IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION; SQL - end + end - def root_configuration_without_database - configuration_without_database.merge( - "username" => "root", - "password" => root_password - ) - end + def root_configuration_without_database + configuration_without_database.merge( + "username" => "root", + "password" => root_password + ) + end - def root_password - $stdout.print "Please provide the root password for your MySQL installation\n>" - $stdin.gets.strip - end + def root_password + $stdout.print "Please provide the root password for your MySQL installation\n>" + $stdin.gets.strip + end - def prepare_command_options - args = { - "host" => "--host", - "port" => "--port", - "socket" => "--socket", - "username" => "--user", - "password" => "--password", - "encoding" => "--default-character-set", - "sslca" => "--ssl-ca", - "sslcert" => "--ssl-cert", - "sslcapath" => "--ssl-capath", - "sslcipher" => "--ssl-cipher", - "sslkey" => "--ssl-key" - }.map { |opt, arg| "#{arg}=#{configuration[opt]}" if configuration[opt] }.compact - - args - end + def prepare_command_options + args = { + "host" => "--host", + "port" => "--port", + "socket" => "--socket", + "username" => "--user", + "password" => "--password", + "encoding" => "--default-character-set", + "sslca" => "--ssl-ca", + "sslcert" => "--ssl-cert", + "sslcapath" => "--ssl-capath", + "sslcipher" => "--ssl-cipher", + "sslkey" => "--ssl-key" + }.map { |opt, arg| "#{arg}=#{configuration[opt]}" if configuration[opt] }.compact + + args + end - def run_cmd(cmd, args, action) - fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args) - end + def run_cmd(cmd, args, action) + fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args) + end - def run_cmd_error(cmd, args, action) - msg = "failed to execute: `#{cmd}`\n" - msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n" - msg - end + def run_cmd_error(cmd, args, action) + msg = "failed to execute: `#{cmd}`\n" + msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n" + msg + end end end end diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb index d290b12b73..c9e7262709 100644 --- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb @@ -74,38 +74,38 @@ module ActiveRecord private - def configuration - @configuration - end + def configuration + @configuration + end - def encoding - configuration["encoding"] || DEFAULT_ENCODING - end + def encoding + configuration["encoding"] || DEFAULT_ENCODING + end - def establish_master_connection - establish_connection configuration.merge( - "database" => "postgres", - "schema_search_path" => "public" - ) - end + def establish_master_connection + establish_connection configuration.merge( + "database" => "postgres", + "schema_search_path" => "public" + ) + end - def set_psql_env - ENV["PGHOST"] = configuration["host"] if configuration["host"] - ENV["PGPORT"] = configuration["port"].to_s if configuration["port"] - ENV["PGPASSWORD"] = configuration["password"].to_s if configuration["password"] - ENV["PGUSER"] = configuration["username"].to_s if configuration["username"] - end + def set_psql_env + ENV["PGHOST"] = configuration["host"] if configuration["host"] + ENV["PGPORT"] = configuration["port"].to_s if configuration["port"] + ENV["PGPASSWORD"] = configuration["password"].to_s if configuration["password"] + ENV["PGUSER"] = configuration["username"].to_s if configuration["username"] + end - def run_cmd(cmd, args, action) - fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args) - end + def run_cmd(cmd, args, action) + fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args) + end - def run_cmd_error(cmd, args, action) - msg = "failed to execute:\n" - msg << "#{cmd} #{args.join(' ')}\n\n" - msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n" - msg - end + def run_cmd_error(cmd, args, action) + msg = "failed to execute:\n" + msg << "#{cmd} #{args.join(' ')}\n\n" + msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n" + msg + end end end end diff --git a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb index 8533fb6aa5..31f1b7efd4 100644 --- a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb @@ -47,13 +47,13 @@ module ActiveRecord private - def configuration - @configuration - end + def configuration + @configuration + end - def root - @root - end + def root + @root + end end end end diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index a140dd236f..b19ae5c46e 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -286,23 +286,23 @@ module ActiveRecord private - def set_options_for_callbacks!(args, enforced_options = {}) - options = args.extract_options!.merge!(enforced_options) - args << options - - if options[:on] - fire_on = Array(options[:on]) - assert_valid_transaction_action(fire_on) - options[:if] = Array(options[:if]) - options[:if] << "transaction_include_any_action?(#{fire_on})" + def set_options_for_callbacks!(args, enforced_options = {}) + options = args.extract_options!.merge!(enforced_options) + args << options + + if options[:on] + fire_on = Array(options[:on]) + assert_valid_transaction_action(fire_on) + options[:if] = Array(options[:if]) + options[:if] << "transaction_include_any_action?(#{fire_on})" + end end - end - def assert_valid_transaction_action(actions) - if (actions - ACTIONS).any? - raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS}" + def assert_valid_transaction_action(actions) + if (actions - ACTIONS).any? + raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS}" + end end - end end # See ActiveRecord::Transactions::ClassMethods for detailed documentation. @@ -410,73 +410,73 @@ module ActiveRecord protected # Save the new record state and id of a record so it can be restored later if a transaction fails. - def remember_transaction_record_state #:nodoc: - @_start_transaction_state[:id] = id - @_start_transaction_state.reverse_merge!( - new_record: @new_record, - destroyed: @destroyed, - frozen?: frozen?, - ) - @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1 - end + def remember_transaction_record_state #:nodoc: + @_start_transaction_state[:id] = id + @_start_transaction_state.reverse_merge!( + new_record: @new_record, + destroyed: @destroyed, + frozen?: frozen?, + ) + @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1 + end # Clear the new record state and id of a record. - def clear_transaction_record_state #:nodoc: - @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 - force_clear_transaction_record_state if @_start_transaction_state[:level] < 1 - end + def clear_transaction_record_state #:nodoc: + @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 + force_clear_transaction_record_state if @_start_transaction_state[:level] < 1 + end # Force to clear the transaction record state. - def force_clear_transaction_record_state #:nodoc: - @_start_transaction_state.clear - end + def force_clear_transaction_record_state #:nodoc: + @_start_transaction_state.clear + end # Restore the new record state and id of a record that was previously saved by a call to save_record_state. - def restore_transaction_record_state(force = false) #:nodoc: - unless @_start_transaction_state.empty? - transaction_level = (@_start_transaction_state[:level] || 0) - 1 - if transaction_level < 1 || force - restore_state = @_start_transaction_state - thaw - @new_record = restore_state[:new_record] - @destroyed = restore_state[:destroyed] - pk = self.class.primary_key - if pk && read_attribute(pk) != restore_state[:id] - write_attribute(pk, restore_state[:id]) + def restore_transaction_record_state(force = false) #:nodoc: + unless @_start_transaction_state.empty? + transaction_level = (@_start_transaction_state[:level] || 0) - 1 + if transaction_level < 1 || force + restore_state = @_start_transaction_state + thaw + @new_record = restore_state[:new_record] + @destroyed = restore_state[:destroyed] + pk = self.class.primary_key + if pk && read_attribute(pk) != restore_state[:id] + write_attribute(pk, restore_state[:id]) + end + freeze if restore_state[:frozen?] end - freeze if restore_state[:frozen?] end end - end # Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed. - def transaction_record_state(state) #:nodoc: - @_start_transaction_state[state] - end + def transaction_record_state(state) #:nodoc: + @_start_transaction_state[state] + end # Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks. - def transaction_include_any_action?(actions) #:nodoc: - actions.any? do |action| - case action - when :create - transaction_record_state(:new_record) - when :destroy - destroyed? - when :update - !(transaction_record_state(:new_record) || destroyed?) + def transaction_include_any_action?(actions) #:nodoc: + actions.any? do |action| + case action + when :create + transaction_record_state(:new_record) + when :destroy + destroyed? + when :update + !(transaction_record_state(:new_record) || destroyed?) + end end end - end private - def set_transaction_state(state) # :nodoc: - @transaction_state = state - end + def set_transaction_state(state) # :nodoc: + @transaction_state = state + end - def has_transactional_callbacks? # :nodoc: - !_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_before_commit_callbacks.empty? - end + def has_transactional_callbacks? # :nodoc: + !_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_before_commit_callbacks.empty? + end # Updates the attributes on this particular Active Record object so that # if it's associated with a transaction, then the state of the Active Record @@ -495,15 +495,15 @@ module ActiveRecord # Since Active Record objects can be inside multiple transactions, this # method recursively goes through the parent of the TransactionState and # checks if the Active Record object reflects the state of the object. - def sync_with_transaction_state - update_attributes_from_transaction_state(@transaction_state) - end + def sync_with_transaction_state + update_attributes_from_transaction_state(@transaction_state) + end - def update_attributes_from_transaction_state(transaction_state) - if transaction_state && transaction_state.finalized? - restore_transaction_record_state if transaction_state.rolledback? - clear_transaction_record_state + def update_attributes_from_transaction_state(transaction_state) + if transaction_state && transaction_state.finalized? + restore_transaction_record_state if transaction_state.rolledback? + clear_transaction_record_state + end end - end end end diff --git a/activerecord/lib/active_record/type/adapter_specific_registry.rb b/activerecord/lib/active_record/type/adapter_specific_registry.rb index 208bc8def5..d0f9581576 100644 --- a/activerecord/lib/active_record/type/adapter_specific_registry.rb +++ b/activerecord/lib/active_record/type/adapter_specific_registry.rb @@ -10,15 +10,15 @@ module ActiveRecord private - def registration_klass - Registration - end + def registration_klass + Registration + end - def find_registration(symbol, *args) - registrations - .select { |registration| registration.matches?(symbol, *args) } - .max - end + def find_registration(symbol, *args) + registrations + .select { |registration| registration.matches?(symbol, *args) } + .max + end end class Registration @@ -52,42 +52,42 @@ module ActiveRecord protected - attr_reader :name, :block, :adapter, :override - - def priority - result = 0 - if adapter - result |= 1 - end - if override - result |= 2 + attr_reader :name, :block, :adapter, :override + + def priority + result = 0 + if adapter + result |= 1 + end + if override + result |= 2 + end + result end - result - end - def priority_except_adapter - priority & 0b111111100 - end + def priority_except_adapter + priority & 0b111111100 + end private - def matches_adapter?(adapter: nil, **) - (self.adapter.nil? || adapter == self.adapter) - end + def matches_adapter?(adapter: nil, **) + (self.adapter.nil? || adapter == self.adapter) + end - def conflicts_with?(other) - same_priority_except_adapter?(other) && - has_adapter_conflict?(other) - end + def conflicts_with?(other) + same_priority_except_adapter?(other) && + has_adapter_conflict?(other) + end - def same_priority_except_adapter?(other) - priority_except_adapter == other.priority_except_adapter - end + def same_priority_except_adapter?(other) + priority_except_adapter == other.priority_except_adapter + end - def has_adapter_conflict?(other) - (override.nil? && other.adapter) || - (adapter && other.override.nil?) - end + def has_adapter_conflict?(other) + (override.nil? && other.adapter) || + (adapter && other.override.nil?) + end end class DecorationRegistration < Registration @@ -112,15 +112,15 @@ module ActiveRecord protected - attr_reader :options, :klass + attr_reader :options, :klass private - def matches_options?(**kwargs) - options.all? do |key, value| - kwargs[key] == value + def matches_options?(**kwargs) + options.all? do |key, value| + kwargs[key] == value + end end - end end end diff --git a/activerecord/lib/active_record/type/hash_lookup_type_map.rb b/activerecord/lib/active_record/type/hash_lookup_type_map.rb index 3b01e3f8ca..0145d5d6c1 100644 --- a/activerecord/lib/active_record/type/hash_lookup_type_map.rb +++ b/activerecord/lib/active_record/type/hash_lookup_type_map.rb @@ -15,9 +15,9 @@ module ActiveRecord private - def perform_fetch(type, *args, &block) - @mapping.fetch(type, block).call(type, *args) - end + def perform_fetch(type, *args, &block) + @mapping.fetch(type, block).call(type, *args) + end end end end diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb index a3a5241780..ac9134bfcb 100644 --- a/activerecord/lib/active_record/type/serialized.rb +++ b/activerecord/lib/active_record/type/serialized.rb @@ -49,15 +49,15 @@ module ActiveRecord private - def default_value?(value) - value == coder.load(nil) - end + def default_value?(value) + value == coder.load(nil) + end - def encoded(value) - unless default_value?(value) - coder.dump(value) + def encoded(value) + unless default_value?(value) + coder.dump(value) + end end - end end end end diff --git a/activerecord/lib/active_record/type/time.rb b/activerecord/lib/active_record/type/time.rb index 7da49e43c7..b9bac87c67 100644 --- a/activerecord/lib/active_record/type/time.rb +++ b/activerecord/lib/active_record/type/time.rb @@ -17,4 +17,3 @@ module ActiveRecord end end end - diff --git a/activerecord/lib/active_record/type/type_map.rb b/activerecord/lib/active_record/type/type_map.rb index 1f744ef115..9618ff8787 100644 --- a/activerecord/lib/active_record/type/type_map.rb +++ b/activerecord/lib/active_record/type/type_map.rb @@ -44,21 +44,21 @@ module ActiveRecord private - def perform_fetch(lookup_key, *args) - matching_pair = @mapping.reverse_each.detect do |key, _| - key === lookup_key - end + def perform_fetch(lookup_key, *args) + matching_pair = @mapping.reverse_each.detect do |key, _| + key === lookup_key + end - if matching_pair - matching_pair.last.call(lookup_key, *args) - else - yield lookup_key, *args + if matching_pair + matching_pair.last.call(lookup_key, *args) + else + yield lookup_key, *args + end end - end - def default_value - @default_value ||= ActiveModel::Type::Value.new - end + def default_value + @default_value ||= ActiveModel::Type::Value.new + end end end end diff --git a/activerecord/lib/active_record/type_caster/connection.rb b/activerecord/lib/active_record/type_caster/connection.rb index 7ed8dcc313..6c54792e26 100644 --- a/activerecord/lib/active_record/type_caster/connection.rb +++ b/activerecord/lib/active_record/type_caster/connection.rb @@ -14,16 +14,16 @@ module ActiveRecord protected - attr_reader :table_name - delegate :connection, to: :@klass + attr_reader :table_name + delegate :connection, to: :@klass private - def column_for(attribute_name) - if connection.schema_cache.data_source_exists?(table_name) - connection.schema_cache.columns_hash(table_name)[attribute_name.to_s] + def column_for(attribute_name) + if connection.schema_cache.data_source_exists?(table_name) + connection.schema_cache.columns_hash(table_name)[attribute_name.to_s] + end end - end end end end diff --git a/activerecord/lib/active_record/type_caster/map.rb b/activerecord/lib/active_record/type_caster/map.rb index 3a367b3999..52529a6b42 100644 --- a/activerecord/lib/active_record/type_caster/map.rb +++ b/activerecord/lib/active_record/type_caster/map.rb @@ -13,7 +13,7 @@ module ActiveRecord protected - attr_reader :types + attr_reader :types end end end 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 66ef62a76c..76182a3c24 100644 --- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb +++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb @@ -15,43 +15,43 @@ module ActiveRecord end protected - attr_reader :migration_action, :join_tables + attr_reader :migration_action, :join_tables # Sets the default migration template that is being used for the generation of the migration. # Depending on command line arguments, the migration template and the table name instance # variables are set up. - def set_local_assigns! - @migration_template = "migration.rb" - case file_name - when /^(add|remove)_.*_(?:to|from)_(.*)/ - @migration_action = $1 - @table_name = normalize_table_name($2) - when /join_table/ - if attributes.length == 2 - @migration_action = "join" - @join_tables = pluralize_table_names? ? attributes.map(&:plural_name) : attributes.map(&:singular_name) + def set_local_assigns! + @migration_template = "migration.rb" + case file_name + when /^(add|remove)_.*_(?:to|from)_(.*)/ + @migration_action = $1 + @table_name = normalize_table_name($2) + when /join_table/ + if attributes.length == 2 + @migration_action = "join" + @join_tables = pluralize_table_names? ? attributes.map(&:plural_name) : attributes.map(&:singular_name) - set_index_names + set_index_names + end + when /^create_(.+)/ + @table_name = normalize_table_name($1) + @migration_template = "create_table_migration.rb" end - when /^create_(.+)/ - @table_name = normalize_table_name($1) - @migration_template = "create_table_migration.rb" end - end - def set_index_names - attributes.each_with_index do |attr, i| - attr.index_name = [attr, attributes[i - 1]].map{ |a| index_name_for(a) } + def set_index_names + attributes.each_with_index do |attr, i| + attr.index_name = [attr, attributes[i - 1]].map{ |a| index_name_for(a) } + end end - end - def index_name_for(attribute) - if attribute.foreign_key? - attribute.name - else - attribute.name.singularize.foreign_key - end.to_sym - end + def index_name_for(attribute) + if attribute.foreign_key? + attribute.name + else + attribute.name.singularize.foreign_key + end.to_sym + end private def attributes_with_index |