aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md15
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb5
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb8
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb68
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb4
-rw-r--r--activerecord/lib/active_record/core.rb6
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb31
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb2
-rw-r--r--activerecord/lib/active_record/persistence.rb30
-rw-r--r--activerecord/lib/active_record/querying.rb1
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb19
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb4
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/migration.rb4
-rw-r--r--activerecord/test/cases/associations/eager_test.rb11
-rw-r--r--activerecord/test/cases/attribute_methods/read_test.rb4
-rw-r--r--activerecord/test/cases/base_test.rb42
-rw-r--r--activerecord/test/cases/locking_test.rb16
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb13
-rw-r--r--activerecord/test/cases/relations_test.rb34
-rw-r--r--activerecord/test/fixtures/peoples_treasures.yml3
-rw-r--r--activerecord/test/models/person.rb6
-rw-r--r--activerecord/test/schema/schema.rb7
24 files changed, 226 insertions, 115 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 46031e7c13..26f6093bc2 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,20 @@
## Rails 4.0.0 (unreleased) ##
+* Added `#find_by` and `#find_by!` to mirror the functionality
+ provided by dynamic finders in a way that allows dynamic input more
+ easily:
+
+ Post.find_by name: 'Spartacus', rating: 4
+ Post.find_by "published_at < ?", 2.weeks.ago
+ Post.find_by! name: 'Spartacus'
+
+ *Jon Leighton*
+
+* Added ActiveRecord::Base#slice to return a hash of the given methods with
+ their names as keys and returned values as values.
+
+ *Guillermo Iguaran*
+
* Deprecate eager-evaluated scopes.
Don't use this:
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 253998fb23..b4c3908b10 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -77,7 +77,7 @@ module ActiveRecord
# 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
sliced = owner_keys.each_slice(model.connection.in_clause_length || owner_keys.size)
- records = sliced.map { |slice| records_for(slice) }.flatten
+ records = sliced.map { |slice| records_for(slice).to_a }.flatten
end
# Each record may have multiple owners, and vice-versa
@@ -93,7 +93,8 @@ module ActiveRecord
end
def build_scope
- scope = klass.scoped
+ scope = klass.unscoped
+ scope.default_scoped = true
scope = scope.where(interpolate(options[:conditions]))
scope = scope.where(interpolate(preload_options[:conditions]))
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index bebf6a1a05..39ea885246 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -49,14 +49,6 @@ module ActiveRecord
@attribute_methods_generated ||= false
end
- # We will define the methods as instance methods, but will call them as singleton
- # methods. This allows us to use method_defined? to check if the method exists,
- # which is fast and won't give any false positives from the ancestors (because
- # there are no ancestors).
- def generated_external_attribute_methods
- @generated_external_attribute_methods ||= Module.new { extend self }
- end
-
def undefine_attribute_methods
super if attribute_methods_generated?
@attribute_methods_generated = false
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index 51389c84d6..7b7811a706 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -43,12 +43,6 @@ module ActiveRecord
if attr_name == primary_key && attr_name != 'id'
generated_attribute_methods.send(:alias_method, :id, primary_key)
- generated_external_attribute_methods.module_eval <<-CODE, __FILE__, __LINE__
- def id(v, attributes, attributes_cache, attr_name)
- attr_name = '#{primary_key}'
- send(attr_name, attributes[attr_name], attributes, attributes_cache, attr_name)
- end
- CODE
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 846ac03d82..dcc3d79de9 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -29,35 +29,8 @@ module ActiveRecord
cached_attributes.include?(attr_name)
end
- def undefine_attribute_methods
- generated_external_attribute_methods.module_eval do
- instance_methods.each { |m| undef_method(m) }
- end
-
- super
- end
-
- def type_cast_attribute(attr_name, attributes, cache = {}) #:nodoc:
- return unless attr_name
- attr_name = attr_name.to_s
-
- if generated_external_attribute_methods.method_defined?(attr_name)
- if attributes.has_key?(attr_name) || attr_name == 'id'
- generated_external_attribute_methods.send(attr_name, attributes[attr_name], attributes, cache, attr_name)
- end
- elsif !attribute_methods_generated?
- # If we haven't generated the caster methods yet, do that and
- # then try again
- define_attribute_methods
- type_cast_attribute(attr_name, attributes, cache)
- else
- # If we get here, the attribute has no associated DB column, so
- # just return it verbatim.
- attributes[attr_name]
- end
- end
-
protected
+
# We want to generate the methods via module_eval rather than define_method,
# because define_method is slower on dispatch and uses more memory (because it
# creates a closure).
@@ -67,19 +40,9 @@ module ActiveRecord
# we first define with the __temp__ identifier, and then use alias method to
# rename it to what we want.
def define_method_attribute(attr_name)
- cast_code = attribute_cast_code(attr_name)
-
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
def __temp__
- #{internal_attribute_access_code(attr_name, cast_code)}
- end
- alias_method '#{attr_name}', :__temp__
- undef_method :__temp__
- STR
-
- generated_external_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
- def __temp__(v, attributes, attributes_cache, attr_name)
- #{external_attribute_access_code(attr_name, cast_code)}
+ read_attribute('#{attr_name}') { |n| missing_attribute(n, caller) }
end
alias_method '#{attr_name}', :__temp__
undef_method :__temp__
@@ -87,6 +50,7 @@ module ActiveRecord
end
private
+
def cacheable_column?(column)
if attribute_types_cached_by_default == ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
! serialized_attributes.include? column.name
@@ -94,33 +58,19 @@ module ActiveRecord
attribute_types_cached_by_default.include?(column.type)
end
end
-
- def internal_attribute_access_code(attr_name, cast_code)
- "read_attribute('#{attr_name}') { |n| missing_attribute(n, caller) }"
- end
-
- def external_attribute_access_code(attr_name, cast_code)
- access_code = "v && #{cast_code}"
-
- if cache_attribute?(attr_name)
- access_code = "attributes_cache[attr_name] ||= (#{access_code})"
- end
-
- access_code
- end
-
- def attribute_cast_code(attr_name)
- columns_hash[attr_name].type_cast_code('v')
- end
end
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
def read_attribute(attr_name)
# If it's cached, just return it
- @attributes_cache.fetch(attr_name) { |name|
+ @attributes_cache.fetch(attr_name.to_s) { |name|
column = @columns_hash.fetch(name) {
- return self.class.type_cast_attribute(name, @attributes, @attributes_cache)
+ return @attributes.fetch(name) {
+ if name == 'id' && self.class.primary_key != name
+ read_attribute(self.class.primary_key)
+ end
+ }
}
value = @attributes.fetch(name) {
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 731c07547a..64f922b7ad 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -526,7 +526,7 @@ module ActiveRecord
execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
create_table = each_hash(result).first[:"Create Table"]
if create_table.to_s =~ /PRIMARY KEY\s+\((.+)\)/
- keys = $1.split(",").map { |key| key.gsub(/[`"]/, "") }
+ keys = $1.split(",").map { |key| key.delete('`"') }
keys.length == 1 ? [keys.first, nil] : nil
else
nil
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 78e54c4c9b..b7e1513422 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -1,4 +1,5 @@
require 'set'
+require 'active_support/deprecation'
module ActiveRecord
# :stopdoc:
@@ -107,6 +108,9 @@ module ActiveRecord
end
def type_cast_code(var_name)
+ ActiveSupport::Deprecation.warn("Column#type_cast_code is deprecated in favor of" \
+ "using Column#type_cast only, and it is going to be removed in future Rails versions.")
+
klass = self.class.name
case type
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 9a2f859fc7..76c424e8b4 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -1,4 +1,5 @@
require 'active_support/concern'
+require 'active_support/core_ext/hash/indifferent_access'
require 'thread'
module ActiveRecord
@@ -326,6 +327,11 @@ module ActiveRecord
"#<#{self.class} #{inspection}>"
end
+ # Returns a hash of the given methods with their names as keys and returned values as values.
+ def slice(*methods)
+ Hash[methods.map { |method| [method, public_send(method)] }].with_indifferent_access
+ end
+
private
# Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 8266427b71..a3412582fa 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -101,24 +101,29 @@ module ActiveRecord
end
end
- def destroy #:nodoc:
- return super unless locking_enabled?
+ def destroy_row
+ affected_rows = super
- if persisted?
- table = self.class.arel_table
- lock_col = self.class.locking_column
- predicate = table[self.class.primary_key].eq(id).
- and(table[lock_col].eq(send(lock_col).to_i))
+ if locking_enabled? && affected_rows != 1
+ raise ActiveRecord::StaleObjectError.new(self, "destroy")
+ end
- affected_rows = self.class.unscoped.where(predicate).delete_all
+ affected_rows
+ end
- unless affected_rows == 1
- raise ActiveRecord::StaleObjectError.new(self, "destroy")
- end
+ def relation_for_destroy
+ relation = super
+
+ if locking_enabled?
+ column_name = self.class.locking_column
+ column = self.class.columns_hash[column_name]
+ substitute = connection.substitute_at(column, relation.bind_values.length)
+
+ relation = relation.where(self.class.arel_table[column_name].eq(substitute))
+ relation.bind_values << [column, self[column_name].to_i]
end
- @destroyed = true
- freeze
+ relation
end
module ClassMethods
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 6bf0becad8..32a1dae6bc 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -288,7 +288,7 @@ module ActiveRecord
# def pirate_attributes=(attributes)
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes, mass_assignment_options)
# end
- class_eval <<-eoruby, __FILE__, __LINE__ + 1
+ generated_feature_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1
if method_defined?(:#{association_name}_attributes=)
remove_method(:#{association_name}_attributes=)
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 35c922e979..bb504ae90f 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -124,19 +124,7 @@ module ActiveRecord
# that no changes should be made (since they can't be persisted).
def destroy
destroy_associations
-
- if persisted?
- pk = self.class.primary_key
- column = self.class.columns_hash[pk]
- substitute = connection.substitute_at(column, 0)
-
- relation = self.class.unscoped.where(
- self.class.arel_table[pk].eq(substitute))
-
- relation.bind_values = [[column, id]]
- relation.delete_all
- end
-
+ destroy_row if persisted?
@destroyed = true
freeze
end
@@ -335,6 +323,22 @@ module ActiveRecord
def destroy_associations
end
+ def destroy_row
+ relation_for_destroy.delete_all
+ end
+
+ def relation_for_destroy
+ pk = self.class.primary_key
+ column = self.class.columns_hash[pk]
+ substitute = connection.substitute_at(column, 0)
+
+ relation = self.class.unscoped.where(
+ self.class.arel_table[pk].eq(substitute))
+
+ relation.bind_values = [[column, id]]
+ relation
+ end
+
def create_or_update
raise ReadOnlyRecord if readonly?
result = new_record? ? create : update
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 0e6fecbc4b..95565b503a 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -5,6 +5,7 @@ module ActiveRecord
module Querying
delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped
delegate :first_or_create, :first_or_create!, :first_or_initialize, :to => :scoped
+ delegate :find_by, :find_by!, :to => :scoped
delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped
delegate :find_each, :find_in_batches, :to => :scoped
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins,
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 2c74f4011d..74f8e30404 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -109,6 +109,25 @@ module ActiveRecord
end
end
+ # Finds the first record matching the specified conditions. There
+ # is no implied ording so if order matters, you should specify it
+ # yourself.
+ #
+ # If no record is found, returns <tt>nil</tt>.
+ #
+ # Post.find_by name: 'Spartacus', rating: 4
+ # Post.find_by "published_at < ?", 2.weeks.ago
+ #
+ def find_by(*args)
+ where(*args).first
+ end
+
+ # Like <tt>find_by</tt>, except that if no record is found, raises
+ # an <tt>ActiveRecord::RecordNotFound</tt> error.
+ def find_by!(*args)
+ where(*args).first!
+ end
+
# 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>.
def first(*args)
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 1088773bc7..b40bf2b3cf 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -39,7 +39,7 @@ module ActiveRecord
attribute.in(value.arel.ast)
when Array, ActiveRecord::Associations::CollectionProxy
values = value.to_a.map {|x| x.is_a?(ActiveRecord::Model) ? x.id : x}
- ranges, values = values.partition {|v| v.is_a?(Range) || v.is_a?(Arel::Relation)}
+ ranges, values = values.partition {|v| v.is_a?(Range)}
values_predicate = if values.include?(nil)
values = values.compact
@@ -59,7 +59,7 @@ module ActiveRecord
array_predicates = ranges.map { |range| attribute.in(range) }
array_predicates << values_predicate
array_predicates.inject { |composite, predicate| composite.or(predicate) }
- when Range, Arel::Relation
+ when Range
attribute.in(value)
when ActiveRecord::Model
attribute.eq(value.id)
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
index 9ca63a209e..b9b5ec7956 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
@@ -13,9 +13,9 @@ class <%= migration_class_name %> < ActiveRecord::Migration
<% attributes.each do |attribute| -%>
<%- if migration_action -%>
<%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type %><%= attribute.inject_options %><% end %>
- <% if attribute.has_index? && migration_action == 'add' %>
+ <%- if attribute.has_index? && migration_action == 'add' -%>
add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
- <% end -%>
+ <%- end -%>
<%- end -%>
<%- end -%>
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index b27a93f857..efdb7cbb36 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -1169,4 +1169,15 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_no_queries { assert_equal 1, posts[0].categories[1].categorizations.length }
assert_no_queries { assert_equal 2, posts[1].categories[0].categorizations.length }
end
+
+ test "scoping with a circular preload" do
+ assert_equal Comment.find(1), Comment.preload(:post => :comments).scoping { Comment.find(1) }
+ end
+
+ test "preload ignores the scoping" do
+ assert_equal(
+ Comment.find(1).post,
+ Post.where('1 = 0').scoping { Comment.preload(:post).find(1).post }
+ )
+ end
end
diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb
index 764305459d..98c38535a6 100644
--- a/activerecord/test/cases/attribute_methods/read_test.rb
+++ b/activerecord/test/cases/attribute_methods/read_test.rb
@@ -6,10 +6,6 @@ module ActiveRecord
module AttributeMethods
class ReadTest < ActiveRecord::TestCase
class FakeColumn < Struct.new(:name)
- def type_cast_code(var)
- var
- end
-
def type; :integer; end
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index fb2f66c4f8..5fb49d540f 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -62,7 +62,11 @@ end
class Weird < ActiveRecord::Base; end
-class Boolean < ActiveRecord::Base; end
+class Boolean < ActiveRecord::Base
+ def has_fun
+ super
+ end
+end
class LintTest < ActiveRecord::TestCase
include ActiveModel::Lint::Tests
@@ -957,6 +961,16 @@ class BasicsTest < ActiveRecord::TestCase
assert b_true.value?
end
+ def test_boolean_without_questionmark
+ b_true = Boolean.create({ "value" => true })
+ true_id = b_true.id
+
+ subclass = Class.new(Boolean).find true_id
+ superclass = Boolean.find true_id
+
+ assert_equal superclass.read_attribute(:has_fun), subclass.read_attribute(:has_fun)
+ end
+
def test_boolean_cast_from_string
b_blank = Boolean.create({ "value" => "" })
blank_id = b_blank.id
@@ -2043,4 +2057,30 @@ class BasicsTest < ActiveRecord::TestCase
def test_typecasting_aliases
assert_equal 10, Topic.select('10 as tenderlove').first.tenderlove
end
+
+ def test_slice
+ company = Company.new(:rating => 1, :name => "37signals", :firm_name => "37signals")
+ hash = company.slice(:name, :rating, "arbitrary_method")
+ assert_equal hash[:name], company.name
+ assert_equal hash['name'], company.name
+ assert_equal hash[:rating], company.rating
+ assert_equal hash['arbitrary_method'], company.arbitrary_method
+ assert_equal hash[:arbitrary_method], company.arbitrary_method
+ assert_nil hash[:firm_name]
+ assert_nil hash['firm_name']
+ end
+
+ ["find_by", "find_by!"].each do |meth|
+ test "#{meth} delegates to scoped" do
+ record = stub
+
+ scope = mock
+ scope.expects(meth).with(:foo, :bar).returns(record)
+
+ klass = Class.new(ActiveRecord::Base)
+ klass.stubs(:scoped => scope)
+
+ assert_equal record, klass.public_send(meth, :foo, :bar)
+ end
+ end
end
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 807274ca67..cc6baa6153 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -9,6 +9,7 @@ require 'models/string_key_object'
require 'models/car'
require 'models/engine'
require 'models/wheel'
+require 'models/treasure'
class LockWithoutDefault < ActiveRecord::Base; end
@@ -22,7 +23,7 @@ class ReadonlyFirstNamePerson < Person
end
class OptimisticLockingTest < ActiveRecord::TestCase
- fixtures :people, :legacy_things, :references, :string_key_objects
+ fixtures :people, :legacy_things, :references, :string_key_objects, :peoples_treasures
def test_non_integer_lock_existing
s1 = StringKeyObject.find("record1")
@@ -230,15 +231,24 @@ class OptimisticLockingTest < ActiveRecord::TestCase
def test_polymorphic_destroy_with_dependencies_and_lock_version
car = Car.create!
-
+
assert_difference 'car.wheels.count' do
car.wheels << Wheel.create!
- end
+ end
assert_difference 'car.wheels.count', -1 do
car.destroy
end
assert car.destroyed?
end
+
+ def test_removing_has_and_belongs_to_many_associations_upon_destroy
+ p = RichPerson.create! first_name: 'Jon'
+ p.treasures.create!
+ assert !p.treasures.empty?
+ p.destroy
+ assert p.treasures.empty?
+ assert RichPerson.connection.select_all("SELECT * FROM peoples_treasures WHERE rich_person_id = 1").empty?
+ end
end
class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 09276a034e..0559bbbe9a 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -172,6 +172,19 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
man.interests_attributes = [{:id => interest.id, :topic => 'gardening'}]
assert_equal man.interests.first.topic, man.interests[0].topic
end
+
+ def test_allows_class_to_override_setter_and_call_super
+ mean_pirate_class = Class.new(Pirate) do
+ accepts_nested_attributes_for :parrot
+ def parrot_attributes=(attrs)
+ super(attrs.merge(:color => "blue"))
+ end
+ end
+ mean_pirate = mean_pirate_class.new
+ mean_pirate.parrot_attributes = { :name => "James" }
+ assert_equal "James", mean_pirate.parrot.name
+ assert_equal "blue", mean_pirate.parrot.color
+ end
end
class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 3edc237c44..25eb7c1672 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1256,4 +1256,38 @@ class RelationTest < ActiveRecord::TestCase
assert topics.loaded?
end
+
+ test "find_by with hash conditions returns the first matching record" do
+ assert_equal posts(:eager_other), Post.order(:id).find_by(author_id: 2)
+ end
+
+ test "find_by with non-hash conditions returns the first matching record" do
+ assert_equal posts(:eager_other), Post.order(:id).find_by("author_id = 2")
+ end
+
+ test "find_by with multi-arg conditions returns the first matching record" do
+ assert_equal posts(:eager_other), Post.order(:id).find_by('author_id = ?', 2)
+ end
+
+ test "find_by returns nil if the record is missing" do
+ assert_equal nil, Post.scoped.find_by("1 = 0")
+ end
+
+ test "find_by! with hash conditions returns the first matching record" do
+ assert_equal posts(:eager_other), Post.order(:id).find_by!(author_id: 2)
+ end
+
+ test "find_by! with non-hash conditions returns the first matching record" do
+ assert_equal posts(:eager_other), Post.order(:id).find_by!("author_id = 2")
+ end
+
+ test "find_by! with multi-arg conditions returns the first matching record" do
+ assert_equal posts(:eager_other), Post.order(:id).find_by!('author_id = ?', 2)
+ end
+
+ test "find_by! raises RecordNotFound if the record is missing" do
+ assert_raises(ActiveRecord::RecordNotFound) do
+ Post.scoped.find_by!("1 = 0")
+ end
+ end
end
diff --git a/activerecord/test/fixtures/peoples_treasures.yml b/activerecord/test/fixtures/peoples_treasures.yml
new file mode 100644
index 0000000000..a72b190d0c
--- /dev/null
+++ b/activerecord/test/fixtures/peoples_treasures.yml
@@ -0,0 +1,3 @@
+michael_diamond:
+ rich_person_id: <%= ActiveRecord::Fixtures.identify(:michael) %>
+ treasure_id: <%= ActiveRecord::Fixtures.identify(:diamond) %>
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index b7d5dabc4f..d5c0b351aa 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -85,3 +85,9 @@ class TightPerson < ActiveRecord::Base
end
class TightDescendant < TightPerson; end
+
+class RichPerson < ActiveRecord::Base
+ self.table_name = 'people'
+
+ has_and_belongs_to_many :treasures, :join_table => 'peoples_treasures'
+end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 428a85ab4e..377fde5c96 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -91,6 +91,7 @@ ActiveRecord::Schema.define do
create_table :booleans, :force => true do |t|
t.boolean :value
+ t.boolean :has_fun, :null => false, :default => false
end
create_table :bulbs, :force => true do |t|
@@ -438,6 +439,7 @@ ActiveRecord::Schema.define do
create_table :parrots, :force => true do |t|
t.column :name, :string
+ t.column :color, :string
t.column :parrot_sti_class, :string
t.column :killer_id, :integer
t.column :created_at, :datetime
@@ -468,6 +470,11 @@ ActiveRecord::Schema.define do
t.timestamps
end
+ create_table :peoples_treasures, :id => false, :force => true do |t|
+ t.column :rich_person_id, :integer
+ t.column :treasure_id, :integer
+ end
+
create_table :pets, :primary_key => :pet_id ,:force => true do |t|
t.string :name
t.integer :owner_id, :integer