diff options
Diffstat (limited to 'activerecord/lib/active_record')
14 files changed, 92 insertions, 114 deletions
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index c263edd2c6..adb6af7165 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -37,7 +37,7 @@ module ActiveRecord # Checks whether record is different to the current target, without loading it def different_target?(record) record.nil? && owner[reflection.foreign_key] || - record.id != owner[reflection.foreign_key] + record && record.id != owner[reflection.foreign_key] end def replace_keys(record) diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index e3f221c773..8bd898d126 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -7,7 +7,7 @@ module ActiveRecord # the record is not persisted? or has just been destroyed. def to_key key = send(self.class.primary_key) - [key] if key + persisted? && key ? [key] : nil end module ClassMethods diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index daa1b2fcd6..99ed273a8a 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1052,20 +1052,15 @@ module ActiveRecord #:nodoc: # Each dynamic finder using <tt>scoped_by_*</tt> is also defined in the class after it # is first invoked, so that future attempts to use it do not run through method_missing. def method_missing(method_id, *arguments, &block) - if match = DynamicFinderMatch.match(method_id) + if match = (DynamicFinderMatch.match(method_id) || DynamicScopeMatch.match(method_id)) attribute_names = match.attribute_names super unless all_attributes_exists?(attribute_names) - if match.finder? - options = arguments.extract_options! - relation = options.any? ? scoped(options) : scoped - relation.send :find_by_attributes, match, attribute_names, *arguments, &block - elsif match.instantiator? - scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block + if arguments.size < attribute_names.size + method_trace = "#{__FILE__}:#{__LINE__}:in `#{method_id}'" + backtrace = [method_trace] + caller + raise ArgumentError, "wrong number of arguments (#{arguments.size} for #{attribute_names.size})", backtrace end - elsif match = DynamicScopeMatch.match(method_id) - attribute_names = match.attribute_names - super unless all_attributes_exists?(attribute_names) - if match.scope? + if match.respond_to?(:scope?) && match.scope? self.class_eval <<-METHOD, __FILE__, __LINE__ + 1 def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args) attributes = Hash[[:#{attribute_names.join(',:')}].zip(args)] # attributes = Hash[[:user_name, :password].zip(args)] @@ -1074,6 +1069,12 @@ module ActiveRecord #:nodoc: end # end METHOD send(method_id, *arguments) + elsif match.finder? + options = arguments.extract_options! + relation = options.any? ? scoped(options) : scoped + relation.send :find_by_attributes, match, attribute_names, *arguments, &block + elsif match.instantiator? + scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block end else super @@ -1638,7 +1639,8 @@ MSG when new_record? "#{self.class.model_name.cache_key}/new" when timestamp = self[:updated_at] - "#{self.class.model_name.cache_key}/#{id}-#{timestamp.to_s(:number)}" + timestamp = timestamp.utc.to_s(:number) + "#{self.class.model_name.cache_key}/#{id}-#{timestamp}" else "#{self.class.model_name.cache_key}/#{id}" end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb index bcd3abc08d..123b3654e6 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb @@ -46,6 +46,12 @@ module ActiveRecord # "database" => "path/to/dbfile" # ) # + # Or a URL: + # + # ActiveRecord::Base.establish_connection( + # "postgres://myuser:mypass@localhost/somedatabase" + # ) + # # The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError # may be returned on an error. def self.establish_connection(spec = nil) @@ -58,6 +64,8 @@ module ActiveRecord when Symbol, String if configuration = configurations[spec.to_s] establish_connection(configuration) + elsif spec.is_a?(String) && hash = connection_url_to_hash(spec) + establish_connection(hash) else raise AdapterNotSpecified, "#{spec} database is not configured" end @@ -81,6 +89,24 @@ module ActiveRecord end end + def self.connection_url_to_hash(url) # :nodoc: + config = URI.parse url + adapter = config.scheme + adapter = "postgresql" if adapter == "postgres" + spec = { :adapter => adapter, + :username => config.user, + :password => config.password, + :port => config.port, + :database => config.path.sub(%r{^/},""), + :host => config.host } + spec.reject!{ |_,value| !value } + if config.query + options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys + spec.merge!(options) + end + spec + end + class << self # Returns the connection currently associated with the class. This can # also be used to "borrow" the connection to do database work unrelated diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 724b2e6d9c..e2a0f63393 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -157,6 +157,11 @@ module ActiveRecord end end + def type_cast(value, column) # :nodoc: + return super unless BigDecimal === value + + value.to_f + end # DATABASE STATEMENTS ====================================== diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index c459846264..507f345ef5 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -1,3 +1,5 @@ +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/class/attribute_accessors" require "active_support/core_ext/array/wrap" module ActiveRecord diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 53617059d0..2dbebfcaf8 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -383,7 +383,7 @@ module ActiveRecord attributes_collection = if keys.include?('id') || keys.include?(:id) Array.wrap(attributes_collection) else - attributes_collection.sort_by { |i, _| i.to_i }.map { |_, attributes| attributes } + attributes_collection.values end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 6258a16d0f..2d0861d5c9 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -408,7 +408,17 @@ module ActiveRecord end def eager_loading? - @should_eager_load ||= (@eager_load_values.any? || (@includes_values.any? && references_eager_loaded_tables?)) + @should_eager_load ||= + @eager_load_values.any? || + @includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?) + end + + # Joins that are also marked for preloading. In which case we should just eager load them. + # Note that this is a naive implementation because we could have strings and symbols which + # represent the same association, but that aren't matched by this. Also, we could have + # nested hashes which partially match, e.g. { :a => :b } & { :a => [:b, :c] } + def joined_includes_values + @includes_values & @joins_values end def ==(other) diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 46ab67d1cf..ec1176e3dd 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -66,11 +66,14 @@ module ActiveRecord records = relation.where(table[primary_key].gteq(start)).all while records.any? + records_size = records.size + primary_key_offset = records.last.id + yield records - break if records.size < batch_size + break if records_size < batch_size - if primary_key_offset = records.last.id + if primary_key_offset records = relation.where(table[primary_key].gt(primary_key_offset)).to_a else raise "Primary key not included in the custom select clause" diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 422d89060b..8cef4e5554 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -193,8 +193,8 @@ module ActiveRecord else relation = relation.where(table[primary_key].eq(id)) if id end - - connection.select_value(relation.to_sql) ? true : false + + connection.select_value(relation.to_sql, "#{name} Exists") ? true : false end protected @@ -243,7 +243,7 @@ module ActiveRecord end def construct_limited_ids_condition(relation) - orders = relation.order_values + orders = relation.order_values.map { |val| val.presence }.compact values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders) relation = relation.dup diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index d17861f407..8bd4732c0c 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -96,11 +96,11 @@ module ActiveRecord relation end - def having(*args) - return self if args.blank? + def having(opts, *rest) + return self if opts.blank? relation = clone - relation.having_values += build_where(*args) + relation.having_values += build_where(opts, rest) relation end diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb index e3185a9c5a..5ad40d8cd9 100644 --- a/activerecord/lib/active_record/serialization.rb +++ b/activerecord/lib/active_record/serialization.rb @@ -10,46 +10,8 @@ module ActiveRecord #:nodoc: options[:except] = Array.wrap(options[:except]).map { |n| n.to_s } options[:except] |= Array.wrap(self.class.inheritance_column) - hash = super(options) - - serializable_add_includes(options) do |association, records, opts| - hash[association] = records.is_a?(Enumerable) ? - records.map { |r| r.serializable_hash(opts) } : - records.serializable_hash(opts) - end - - hash + super(options) end - - private - # Add associations specified via the <tt>:include</tt> option. - # - # Expects a block that takes as arguments: - # +association+ - name of the association - # +records+ - the association record(s) to be serialized - # +opts+ - options for the association records - def serializable_add_includes(options = {}) - return unless include_associations = options.delete(:include) - - include_has_options = include_associations.is_a?(Hash) - associations = include_has_options ? include_associations.keys : Array.wrap(include_associations) - - associations.each do |association| - records = case self.class.reflect_on_association(association).macro - when :has_many, :has_and_belongs_to_many - send(association).to_a - when :has_one, :belongs_to - send(association) - end - - if records - association_options = include_has_options ? include_associations[association] : {} - yield(association, records, association_options) - end - end - - options[:include] = include_associations - end end end diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb index f8e6cf958c..cbfa1ad609 100644 --- a/activerecord/lib/active_record/serializers/xml_serializer.rb +++ b/activerecord/lib/active_record/serializers/xml_serializer.rb @@ -182,48 +182,6 @@ module ActiveRecord #:nodoc: options[:except] |= Array.wrap(@serializable.class.inheritance_column) end - def add_extra_behavior - add_includes - end - - def add_includes - procs = options.delete(:procs) - @serializable.send(:serializable_add_includes, options) do |association, records, opts| - add_associations(association, records, opts) - end - options[:procs] = procs - end - - # TODO This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well. - def add_associations(association, records, opts) - association_name = association.to_s.singularize - merged_options = options.merge(opts).merge!(:root => association_name, :skip_instruct => true) - - if records.is_a?(Enumerable) - tag = ActiveSupport::XmlMini.rename_key(association.to_s, options) - type = options[:skip_types] ? { } : {:type => "array"} - - if records.empty? - @builder.tag!(tag, type) - else - @builder.tag!(tag, type) do - records.each do |record| - if options[:skip_types] - record_type = {} - else - record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name - record_type = {:type => record_class} - end - - record.to_xml merged_options.merge(record_type) - end - end - end - elsif record = @serializable.send(association) - record.to_xml(merged_options) - end - end - class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc: def compute_type klass = @serializable.class diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb index 929559c3ba..76c37cc367 100644 --- a/activerecord/lib/active_record/session_store.rb +++ b/activerecord/lib/active_record/session_store.rb @@ -183,11 +183,6 @@ module ActiveRecord ## # :singleton-method: - # Use the ActiveRecord::Base.connection by default. - cattr_accessor :connection - - ## - # :singleton-method: # The table name defaults to 'sessions'. cattr_accessor :table_name @@table_name = 'sessions' @@ -206,10 +201,19 @@ module ActiveRecord class << self alias :data_column_name :data_column + + # Use the ActiveRecord::Base.connection by default. + attr_writer :connection + + # Use the ActiveRecord::Base.connection_pool by default. + attr_writer :connection_pool - remove_method :connection def connection - @@connection ||= ActiveRecord::Base.connection + @connection ||= ActiveRecord::Base.connection + end + + def connection_pool + @connection_pool ||= ActiveRecord::Base.connection_pool end # Look up a session by id and unmarshal its data if found. @@ -219,6 +223,8 @@ module ActiveRecord end end end + + delegate :connection, :connection=, :connection_pool, :connection_pool=, :to => self attr_reader :session_id, :new_record alias :new_record? :new_record @@ -297,8 +303,12 @@ module ActiveRecord private def get_session(env, sid) Base.silence do - sid ||= generate_sid - session = find_session(sid) + unless sid and session = @@session_class.find_by_session_id(sid) + # If the sid was nil or if there is no pre-existing session under the sid, + # force the generation of a new sid and associate a new session associated with the new sid + sid = generate_sid + session = @@session_class.new(:session_id => sid, :data => {}) + end env[SESSION_RECORD_KEY] = session [sid, session.data] end |