diff options
Diffstat (limited to 'activerecord')
7 files changed, 55 insertions, 124 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 97fdd9b1ba..31d3a89b9d 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1273,9 +1273,16 @@ module ActiveRecord if send(reflection.name).loaded? || reflection.options[:finder_sql] send(reflection.name).map(&:id) else - send(reflection.name).all(:select => "#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map(&:id) + if reflection.through_reflection && reflection.source_reflection.belongs_to? + through = reflection.through_reflection + primary_key = reflection.source_reflection.primary_key_name + send(through.name).all(:select => "DISTINCT #{through.quoted_table_name}.#{primary_key}").map!(&:"#{primary_key}") + else + send(reflection.name).all(:select => "#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map!(&:id) + end end end + end def collection_accessor_methods(reflection, association_proxy_class, writer = true) diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 15358979c2..ecd2d57a5a 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -133,6 +133,7 @@ module ActiveRecord end private + # Suffixes a, ?, c become regexp /(a|\?|c)$/ def rebuild_attribute_method_regexp suffixes = attribute_method_suffixes.map { |s| Regexp.escape(s) } @@ -238,19 +239,17 @@ module ActiveRecord def method_missing(method_id, *args, &block) method_name = method_id.to_s - if self.class.private_method_defined?(method_name) - raise NoMethodError.new("Attempt to call private method", method_name, args) - end - # If we haven't generated any methods yet, generate them, then # see if we've created the method we're looking for. if !self.class.generated_methods? self.class.define_attribute_methods + guard_private_attribute_method!(method_name, args) if self.class.generated_methods.include?(method_name) return self.send(method_id, *args, &block) end end + guard_private_attribute_method!(method_name, args) if self.class.primary_key.to_s == method_name id elsif md = self.class.match_attribute_method?(method_name) @@ -371,6 +370,12 @@ module ActiveRecord end private + # prevent method_missing from calling private methods with #send + def guard_private_attribute_method!(method_name, args) + if self.class.private_method_defined?(method_name) + raise NoMethodError.new("Attempt to call private method", method_name, args) + end + end def missing_attribute(attr_name, stack) raise ActiveRecord::MissingAttributeError, "missing attribute: #{attr_name}", stack diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 2d90ef35aa..5d88012e4f 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -78,11 +78,14 @@ HEADER begin tbl = StringIO.new + # first dump primary key column if @connection.respond_to?(:pk_and_sequence_for) pk, pk_seq = @connection.pk_and_sequence_for(table) + elsif @connection.respond_to?(:primary_key) + pk = @connection.primary_key(table) end pk ||= 'id' - + tbl.print " create_table #{table.inspect}" if columns.detect { |c| c.name == pk } if pk != 'id' @@ -94,6 +97,7 @@ HEADER tbl.print ", :force => true" tbl.puts " do |t|" + # then dump all non-primary key columns column_specs = columns.map do |column| raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil? next if column.name == pk diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb index c3811caa53..253fa03785 100644 --- a/activerecord/lib/active_record/serializers/xml_serializer.rb +++ b/activerecord/lib/active_record/serializers/xml_serializer.rb @@ -164,42 +164,9 @@ module ActiveRecord #:nodoc: end end - class XmlSerializer < ActiveModel::Serializer #:nodoc: + class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc: include Serialization::RecordSerializer - def builder - @builder ||= begin - require 'builder' unless defined? ::Builder - options[:indent] ||= 2 - builder = options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent]) - - unless options[:skip_instruct] - builder.instruct! - options[:skip_instruct] = true - end - - builder - end - end - - def root - root = (options[:root] || @serializable.class.to_s.underscore).to_s - reformat_name(root) - end - - def dasherize? - !options.has_key?(:dasherize) || options[:dasherize] - end - - def camelize? - options.has_key?(:camelize) && options[:camelize] - end - - def reformat_name(name) - name = name.camelize if camelize? - dasherize? ? name.dasherize : name - end - def serializable_attributes serializable_attribute_names.collect { |name| Attribute.new(name, @serializable) } end @@ -211,28 +178,6 @@ module ActiveRecord #:nodoc: end end - def add_attributes - (serializable_attributes + serializable_method_attributes).each do |attribute| - add_tag(attribute) - end - end - - def add_procs - if procs = options.delete(:procs) - [ *procs ].each do |proc| - proc.call(options) - end - end - end - - def add_tag(attribute) - builder.tag!( - reformat_name(attribute.name), - attribute.value.to_s, - attribute.decorations(!options[:skip_types]) - ) - end - def add_associations(association, records, opts) if records.is_a?(Enumerable) tag = reformat_name(association.to_s) @@ -282,50 +227,10 @@ module ActiveRecord #:nodoc: end end - class Attribute #:nodoc: - attr_reader :name, :value, :type - - def initialize(name, record) - @name, @record = name, record - - @type = compute_type - @value = compute_value - end - - # There is a significant speed improvement if the value - # does not need to be escaped, as <tt>tag!</tt> escapes all values - # to ensure that valid XML is generated. For known binary - # values, it is at least an order of magnitude faster to - # Base64 encode binary values and directly put them in the - # output XML than to pass the original value or the Base64 - # encoded value to the <tt>tag!</tt> method. It definitely makes - # no sense to Base64 encode the value and then give it to - # <tt>tag!</tt>, since that just adds additional overhead. - def needs_encoding? - ![ :binary, :date, :datetime, :boolean, :float, :integer ].include?(type) - end - - def decorations(include_types = true) - decorations = {} - - if type == :binary - decorations[:encoding] = 'base64' - end - - if include_types && type != :string - decorations[:type] = type - end - - if value.nil? - decorations[:nil] = true - end - - decorations - end - + class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc: protected def compute_type - type = @record.class.serialized_attributes.has_key?(name) ? :yaml : @record.class.columns_hash[name].type + type = @serializable.class.serialized_attributes.has_key?(name) ? :yaml : @serializable.class.columns_hash[name].type case type when :text @@ -336,22 +241,12 @@ module ActiveRecord #:nodoc: type end end - - def compute_value - value = @record.send(name) - - if formatter = Hash::XML_FORMATTING[type.to_s] - value ? formatter.call(value) : nil - else - value - end - end end class MethodAttribute < Attribute #:nodoc: protected def compute_type - Hash::XML_TYPE_NAMES[@record.send(name).class.name] || :string + Hash::XML_TYPE_NAMES[@serializable.send(name).class.name] || :string end end end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 7a4712d7c8..8529ff0285 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -243,8 +243,12 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_equal 2, people(:michael).jobs.size end - def test_get_ids - assert_equal [posts(:welcome).id, posts(:authorless).id].sort, people(:michael).post_ids.sort + def test_get_ids_for_belongs_to_source + assert_sql(/DISTINCT/) { assert_equal [posts(:welcome).id, posts(:authorless).id].sort, people(:michael).post_ids.sort } + end + + def test_get_ids_for_has_many_source + assert_equal [comments(:eager_other_comment1).id], authors(:mary).comment_ids end def test_get_ids_for_loaded_associations diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 17ed302465..183be1e2f9 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -277,6 +277,22 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_raise(ActiveRecord::UnknownAttributeError) { @target.new.attributes = { :title => "Ants in pants" } } end + def test_read_attribute_overwrites_private_method_not_considered_implemented + # simulate a model with a db column that shares its name an inherited + # private method (e.g. Object#system) + # + Object.class_eval do + private + def title; "private!"; end + end + assert !@target.instance_method_already_implemented?(:title) + topic = @target.new + assert_equal nil, topic.title + + Object.send(:undef_method, :title) # remove test method from object + end + + private def time_related_columns_on_topic Topic.columns.select{|c| [:time, :date, :datetime, :timestamp].include?(c.type)}.map(&:name) diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 4f02be3c06..9612b0beb6 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -156,6 +156,13 @@ class SchemaDumperTest < ActiveRecord::TestCase index_definition = standard_dump.split(/\n/).grep(/add_index.*companies/).first.strip assert_equal 'add_index "companies", ["firm_id", "type", "rating", "ruby_type"], :name => "company_index"', index_definition end + + def test_schema_dump_should_honor_nonstandard_primary_keys + output = standard_dump + match = output.match(%r{create_table "movies"(.*)do}) + assert_not_nil(match, "nonstandardpk table not found") + assert_match %r(:primary_key => "movieid"), match[1], "non-standard primary key not preserved" + end if current_adapter?(:MysqlAdapter) def test_schema_dump_should_not_add_default_value_for_mysql_text_field @@ -163,13 +170,6 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{t.text\s+"body",\s+:null => false$}, output end - def test_mysql_schema_dump_should_honor_nonstandard_primary_keys - output = standard_dump - match = output.match(%r{create_table "movies"(.*)do}) - assert_not_nil(match, "nonstandardpk table not found") - assert_match %r(:primary_key => "movieid"), match[1], "non-standard primary key not preserved" - end - def test_schema_dump_includes_length_for_mysql_blob_and_text_fields output = standard_dump assert_match %r{t.binary\s+"tiny_blob",\s+:limit => 255$}, output |