diff options
Diffstat (limited to 'activerecord/lib')
12 files changed, 172 insertions, 139 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index cf439b0dc0..728dec8925 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -55,6 +55,7 @@ module ActiveRecord autoload :FinderMethods autoload :CalculationMethods autoload :PredicateBuilder + autoload :SpawnMethods end autoload :Base diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 80e6acf34c..b2ae447d5e 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1465,8 +1465,7 @@ module ActiveRecord after_destroy(method_name) end - def find_with_associations(options = {}, join_dependency = nil) - join_dependency ||= JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins]) + def find_with_associations(options, join_dependency) rows = select_all_rows(options, join_dependency) join_dependency.instantiate(rows) rescue ThrowResult @@ -1770,84 +1769,6 @@ module ActiveRecord relation.to_sql end - def tables_in_string(string) - return [] if string.blank? - string.scan(/([a-zA-Z_][\.\w]+).?\./).flatten - end - - def tables_in_hash(hash) - return [] if hash.blank? - tables = hash.map do |key, value| - if value.is_a?(Hash) - key.to_s - else - tables_in_string(key) if key.is_a?(String) - end - end - tables.flatten.compact - end - - def conditions_tables(options) - # look in both sets of conditions - conditions = [scope(:find, :conditions), options[:conditions]].inject([]) do |all, cond| - case cond - when nil then all - when Array then all << tables_in_string(cond.first) - when Hash then all << tables_in_hash(cond) - else all << tables_in_string(cond) - end - end - conditions.flatten - end - - def order_tables(options) - order = [options[:order], scope(:find, :order) ].join(", ") - return [] unless order && order.is_a?(String) - tables_in_string(order) - end - - def selects_tables(options) - select = options[:select] - return [] unless select && select.is_a?(String) - tables_in_string(select) - end - - def joined_tables(options) - scope = scope(:find) - joins = options[:joins] - merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins]) - [table_name] + case merged_joins - when Symbol, Hash, Array - if array_of_strings?(merged_joins) - tables_in_string(merged_joins.join(' ')) - else - join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_joins, nil) - join_dependency.join_associations.collect {|join_association| [join_association.aliased_join_table_name, join_association.aliased_table_name]}.flatten.compact - end - else - tables_in_string(merged_joins) - end - end - - # Checks if the conditions reference a table other than the current model table - def include_eager_conditions?(options, tables = nil, joined_tables = nil) - ((tables || conditions_tables(options)) - (joined_tables || joined_tables(options))).any? - end - - # Checks if the query order references a table other than the current model's table. - def include_eager_order?(options, tables = nil, joined_tables = nil) - ((tables || order_tables(options)) - (joined_tables || joined_tables(options))).any? - end - - def include_eager_select?(options, joined_tables = nil) - (selects_tables(options) - (joined_tables || joined_tables(options))).any? - end - - def references_eager_loaded_tables?(options) - joined_tables = joined_tables(options) - include_eager_order?(options, nil, joined_tables) || include_eager_conditions?(options, nil, joined_tables) || include_eager_select?(options, joined_tables) - end - def using_limitable_reflections?(reflections) reflections.reject { |r| [ :belongs_to, :has_one ].include?(r.macro) }.length.zero? end diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 1ceb0dbf96..358db6df1d 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -21,7 +21,7 @@ module ActiveRecord construct_sql end - delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :lock, :readonly, :having, :to => :scoped + delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped def select(select = nil, &block) if block_given? @@ -58,7 +58,7 @@ module ActiveRecord find_scope = construct_scope[:find].slice(:conditions, :order) with_scope(:find => find_scope) do - relation = @reflection.klass.send(:construct_finder_arel_with_includes, options) + relation = @reflection.klass.send(:construct_finder_arel, options) case args.first when :first, :last, :all diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 44c668b619..98ab64537e 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -267,7 +267,7 @@ module ActiveRecord unless valid = association.valid? if reflection.options[:autosave] association.errors.each do |attribute, message| - attribute = "#{reflection.name}_#{attribute}" + attribute = "#{reflection.name}.#{attribute}" errors[attribute] << message if errors[attribute].empty? end else diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 78b92be849..026bf55aaa 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -645,7 +645,7 @@ module ActiveRecord #:nodoc: options = args.extract_options! set_readonly_option!(options) - relation = construct_finder_arel_with_includes(options) + relation = construct_finder_arel(options) case args.first when :first, :last, :all @@ -655,7 +655,7 @@ module ActiveRecord #:nodoc: end end - delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :lock, :readonly, :having, :to => :scoped + delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the # same arguments to this method as you can to <tt>find(:first)</tt>. @@ -1510,11 +1510,17 @@ module ActiveRecord #:nodoc: end def active_relation_table(table_name_alias = nil) - Arel::Table.new(table_name, :as => table_name_alias) + Arel::Table.new(table_name, :as => table_name_alias, :engine => active_relation_engine) end def active_relation_engine - @active_relation_engine ||= Arel::Sql::Engine.new(self) + @active_relation_engine ||= begin + if self == ActiveRecord::Base + Arel::Table.engine + else + connection_handler.connection_pools[name] ? Arel::Sql::Engine.new(self) : superclass.active_relation_engine + end + end end private @@ -1579,7 +1585,8 @@ module ActiveRecord #:nodoc: order(construct_order(options[:order], scope)). limit(construct_limit(options[:limit], scope)). offset(construct_offset(options[:offset], scope)). - from(options[:from]) + from(options[:from]). + includes( merge_includes(scope && scope[:include], options[:include])) lock = (scope && scope[:lock]) || options[:lock] relation = relation.lock if lock.present? @@ -1589,21 +1596,6 @@ module ActiveRecord #:nodoc: relation end - def construct_finder_arel_with_includes(options = {}) - relation = construct_finder_arel(options) - include_associations = merge_includes(scope(:find, :include), options[:include]) - - if include_associations.any? - if references_eager_loaded_tables?(options) - relation = relation.eager_load(include_associations) - else - relation = relation.preload(include_associations) - end - end - - relation - end - def construct_join(joins, scope) merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins]) case merged_joins @@ -1722,7 +1714,7 @@ module ActiveRecord #:nodoc: super unless all_attributes_exists?(attribute_names) if match.finder? options = arguments.extract_options! - relation = options.any? ? construct_finder_arel_with_includes(options) : scoped + relation = options.any? ? construct_finder_arel(options) : scoped relation.send :find_by_attributes, match, attribute_names, *arguments elsif match.instantiator? scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index d09aa3c4d2..5eedf448a4 100755 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -201,7 +201,7 @@ module ActiveRecord protected def log(sql, name) result = nil - ActiveSupport::Notifications.instrument(:sql, :sql => sql, :name => name) do + ActiveSupport::Notifications.instrument("active_record.sql", :sql => sql, :name => name) do @runtime += Benchmark.ms { result = yield } end result diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml index 092f5f0023..e33d389f8c 100644 --- a/activerecord/lib/active_record/locale/en.yml +++ b/activerecord/lib/active_record/locale/en.yml @@ -1,6 +1,9 @@ en: activerecord: errors: + # model.errors.full_messages format. + format: "{{attribute}} {{message}}" + # The values :model, :attribute and :value are always available for interpolation # The value :count is available when applicable. Can be used for pluralization. messages: diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 5959265d5e..f63b249241 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -29,7 +29,7 @@ module ActiveRecord unless scoped?(:find) finder_needs_type_condition? ? active_relation.where(type_condition) : active_relation.spawn else - construct_finder_arel_with_includes + construct_finder_arel end end end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 657ee738c0..a35edace19 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -62,7 +62,7 @@ module ActiveRecord initializer "active_record.notifications" do require 'active_support/notifications' - ActiveSupport::Notifications.subscribe("sql") do |name, before, after, instrumenter_id, payload| + ActiveSupport::Notifications.subscribe("active_record.sql") do |name, before, after, instrumenter_id, payload| ActiveRecord::Base.connection.log_info(payload[:sql], payload[:name], (after - before) * 1000) end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 8756695d46..487b54f27d 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -1,36 +1,32 @@ module ActiveRecord class Relation - include QueryMethods, FinderMethods, CalculationMethods + include QueryMethods, FinderMethods, CalculationMethods, SpawnMethods - delegate :to_sql, :to => :relation delegate :length, :collect, :map, :each, :all?, :to => :to_a - attr_reader :relation, :klass, :preload_associations, :eager_load_associations - attr_writer :readonly, :preload_associations, :eager_load_associations, :table + attr_reader :relation, :klass + attr_writer :readonly, :table + attr_accessor :preload_associations, :eager_load_associations, :includes_associations, :create_with_attributes def initialize(klass, relation) @klass, @relation = klass, relation @preload_associations = [] @eager_load_associations = [] + @includes_associations = [] @loaded, @readonly = false end - def merge(r) - raise ArgumentError, "Cannot merge a #{r.klass.name} relation with #{@klass.name} relation" if r.klass != @klass + def new(*args, &block) + with_create_scope { @klass.new(*args, &block) } + end - joins(r.relation.joins(r.relation)). - group(r.send(:group_clauses).join(', ')). - order(r.send(:order_clauses).join(', ')). - where(r.send(:where_clause)). - limit(r.taken). - offset(r.skipped). - select(r.send(:select_clauses).join(', ')). - eager_load(r.eager_load_associations). - preload(r.preload_associations). - from(r.send(:sources).present? ? r.send(:from_clauses) : nil) + def create(*args, &block) + with_create_scope { @klass.create(*args, &block) } end - alias :& :merge + def create!(*args, &block) + with_create_scope { @klass.create!(*args, &block) } + end def respond_to?(method, include_private = false) return true if @relation.respond_to?(method, include_private) || Array.method_defined?(method) @@ -47,19 +43,21 @@ module ActiveRecord def to_a return @records if loaded? - @records = if @eager_load_associations.any? + find_with_associations = @eager_load_associations.any? || references_eager_loaded_tables? + + @records = if find_with_associations begin @klass.send(:find_with_associations, { :select => @relation.send(:select_clauses).join(', '), :joins => @relation.joins(relation), :group => @relation.send(:group_clauses).join(', '), - :order => @relation.send(:order_clauses).join(', '), + :order => order_clause, :conditions => where_clause, :limit => @relation.taken, :offset => @relation.skipped, :from => (@relation.send(:from_clauses) if @relation.send(:sources).present?) }, - ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations, nil)) + ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations + @includes_associations, nil)) rescue ThrowResult [] end @@ -67,7 +65,10 @@ module ActiveRecord @klass.find_by_sql(@relation.to_sql) end - @preload_associations.each {|associations| @klass.send(:preload_associations, @records, associations) } + preload = @preload_associations + preload += @includes_associations unless find_with_associations + preload.each {|associations| @klass.send(:preload_associations, @records, associations) } + @records.each { |record| record.readonly! } if @readonly @loaded = true @@ -123,28 +124,23 @@ module ActiveRecord end def reset - @first = @last = nil + @first = @last = @to_sql = @order_clause = @scope_for_create = nil @records = [] self end - def spawn(relation = @relation) - relation = self.class.new(@klass, relation) - relation.readonly = @readonly - relation.preload_associations = @preload_associations - relation.eager_load_associations = @eager_load_associations - relation.table = table - relation - end - def table - @table ||= Arel::Table.new(@klass.table_name, Arel::Sql::Engine.new(@klass)) + @table ||= Arel::Table.new(@klass.table_name, :engine => @klass.active_relation_engine) end def primary_key @primary_key ||= table[@klass.primary_key] end + def to_sql + @to_sql ||= @relation.to_sql + end + protected def method_missing(method, *args, &block) @@ -166,9 +162,36 @@ module ActiveRecord end end + def with_create_scope + @klass.send(:with_scope, :create => scope_for_create) { yield } + end + + def scope_for_create + @scope_for_create ||= begin + @create_with_attributes || wheres.inject({}) do |hash, where| + hash[where.operand1.name] = where.operand2.value if where.is_a?(Arel::Predicates::Equality) + hash + end + end + end + def where_clause(join_string = " AND ") @relation.send(:where_clauses).join(join_string) end + def order_clause + @order_clause ||= @relation.send(:order_clauses).join(', ') + end + + def references_eager_loaded_tables? + joined_tables = (tables_in_string(@relation.joins(relation)) + [table.name, table.table_alias]).compact.uniq + (tables_in_string(to_sql) - joined_tables).any? + end + + def tables_in_string(string) + return [] if string.blank? + string.scan(/([a-zA-Z_][\.\w]+).?\./).flatten.uniq + end + end end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 4600aab574..5d7bf0b7bc 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -5,6 +5,10 @@ module ActiveRecord spawn.tap {|r| r.preload_associations += Array.wrap(associations) } end + def includes(*associations) + spawn.tap {|r| r.includes_associations += Array.wrap(associations) } + end + def eager_load(*associations) spawn.tap {|r| r.eager_load_associations += Array.wrap(associations) } end @@ -13,6 +17,10 @@ module ActiveRecord spawn.tap {|r| r.readonly = status } end + def create_with(attributes = {}) + spawn.tap {|r| r.create_with_attributes = attributes } + end + def select(selects) if selects.present? relation = spawn(@relation.project(selects)) diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb new file mode 100644 index 0000000000..4ecee8c634 --- /dev/null +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -0,0 +1,85 @@ +module ActiveRecord + module SpawnMethods + def spawn(relation = @relation) + relation = Relation.new(@klass, relation) + relation.readonly = @readonly + relation.preload_associations = @preload_associations + relation.eager_load_associations = @eager_load_associations + relation.includes_associations = @includes_associations + relation.create_with_attributes = @create_with_attributes + relation.table = table + relation + end + + def merge(r) + raise ArgumentError, "Cannot merge a #{r.klass.name} relation with #{@klass.name} relation" if r.klass != @klass + + merged_relation = spawn(table).eager_load(r.eager_load_associations).preload(r.preload_associations).includes(r.includes_associations) + merged_relation.readonly = r.readonly + + [self.relation, r.relation].each do |arel| + merged_relation = merged_relation. + joins(arel.joins(arel)). + group(arel.groupings). + limit(arel.taken). + offset(arel.skipped). + select(arel.send(:select_clauses)). + from(arel.sources). + having(arel.havings). + lock(arel.locked) + end + + relation_order = r.send(:order_clause) + merged_order = relation_order.present? ? relation_order : order_clause + merged_relation = merged_relation.order(merged_order) + + merged_relation.create_with_attributes = @create_with_attributes + + if @create_with_attributes && r.create_with_attributes + merged_relation.create_with_attributes = @create_with_attributes.merge(r.create_with_attributes) + else + merged_relation.create_with_attributes = r.create_with_attributes || @create_with_attributes + end + + merged_wheres = @relation.wheres + + r.wheres.each do |w| + if w.is_a?(Arel::Predicates::Equality) + merged_wheres = merged_wheres.reject {|p| p.is_a?(Arel::Predicates::Equality) && p.operand1.name == w.operand1.name } + end + + merged_wheres << w + end + + merged_relation.where(*merged_wheres) + end + + alias :& :merge + + def except(*skips) + result = Relation.new(@klass, table) + result.table = table + + [:eager_load, :preload, :includes].each do |load_method| + result = result.send(load_method, send(:"#{load_method}_associations")) + end + + result.readonly = self.readonly unless skips.include?(:readonly) + result.create_with_attributes = @create_with_attributes unless skips.include?(:create_with) + + result = result.joins(@relation.joins(@relation)) unless skips.include?(:joins) + result = result.group(@relation.groupings) unless skips.include?(:group) + result = result.limit(@relation.taken) unless skips.include?(:limit) + result = result.offset(@relation.skipped) unless skips.include?(:offset) + result = result.select(@relation.send(:select_clauses)) unless skips.include?(:select) + result = result.from(@relation.sources) unless skips.include?(:from) + result = result.order(order_clause) unless skips.include?(:order) + result = result.where(*@relation.wheres) unless skips.include?(:where) + result = result.having(*@relation.havings) unless skips.include?(:having) + result = result.lock(@relation.locked) unless skips.include?(:lock) + + result + end + + end +end |