aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG122
-rw-r--r--activerecord/README.rdoc2
-rw-r--r--activerecord/lib/active_record/associations.rb2
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/singular_association.rb25
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb113
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb27
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb5
-rw-r--r--activerecord/lib/active_record/base.rb34
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb13
-rw-r--r--activerecord/lib/active_record/fixtures.rb58
-rw-r--r--activerecord/lib/active_record/identity_map.rb32
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb2
-rw-r--r--activerecord/lib/active_record/persistence.rb2
-rw-r--r--activerecord/lib/active_record/railties/controller_runtime.rb10
-rw-r--r--activerecord/lib/active_record/railties/databases.rake12
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb11
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb7
-rw-r--r--activerecord/lib/active_record/session_store.rb6
-rw-r--r--activerecord/lib/active_record/test_case.rb7
-rw-r--r--activerecord/lib/active_record/validations.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb50
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb21
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb4
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb80
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb88
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb3
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb12
-rw-r--r--activerecord/test/cases/associations_test.rb2
-rw-r--r--activerecord/test/cases/base_test.rb24
-rw-r--r--activerecord/test/cases/calculations_test.rb11
-rw-r--r--activerecord/test/cases/finder_test.rb21
-rw-r--r--activerecord/test/cases/fixtures_test.rb4
-rw-r--r--activerecord/test/cases/locking_test.rb36
-rw-r--r--activerecord/test/cases/mass_assignment_security_test.rb82
-rw-r--r--activerecord/test/cases/method_scoping_test.rb22
-rw-r--r--activerecord/test/cases/named_scope_test.rb2
-rw-r--r--activerecord/test/cases/persistence_test.rb4
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb4
-rw-r--r--activerecord/test/cases/session_store/session_test.rb6
-rw-r--r--activerecord/test/cases/timestamp_test.rb26
-rw-r--r--activerecord/test/cases/xml_serialization_test.rb5
-rw-r--r--activerecord/test/fixtures/all/people.yml (renamed from activerecord/test/fixtures/all/people.csv)0
-rw-r--r--activerecord/test/fixtures/memberships.yml7
-rw-r--r--activerecord/test/fixtures/string_key_objects.yml7
-rw-r--r--activerecord/test/models/aircraft.rb3
-rw-r--r--activerecord/test/models/bulb.rb19
-rw-r--r--activerecord/test/models/car.rb5
-rw-r--r--activerecord/test/models/member.rb2
-rw-r--r--activerecord/test/models/membership.rb6
-rw-r--r--activerecord/test/models/string_key_object.rb3
-rw-r--r--activerecord/test/schema/schema.rb12
56 files changed, 855 insertions, 241 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index a03751a6c1..502a7e43de 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,6 +1,20 @@
*Rails 3.1.0 (unreleased)*
-* AR#new, AR#create and AR#update_attributes all accept a second hash as option that allows you
+* Add block setting of attributes to singular associations:
+
+ class User < ActiveRecord::Base
+ has_one :account
+ end
+
+ user.build_account{ |a| a.credit_limit => 100.0 }
+
+ The block is called after the instance has been initialized. [Andrew White]
+
+* Add ActiveRecord::Base.attribute_names to return a list of attribute names. This will return an empty array if the model is abstract or table does not exists. [Prem Sichanugrist]
+
+* CSV Fixtures are deprecated and support will be removed in Rails 3.2.0
+
+* AR#new, AR#create, AR#create!, AR#update_attributes and AR#update_attributes! all accept a second hash as option that allows you
to specify which role to consider when assigning attributes. This is built on top of ActiveModel's
new mass assignment capabilities:
@@ -12,7 +26,9 @@
Post.new(params[:post], :as => :admin)
assign_attributes() with similar API was also added and attributes=(params, guard) was deprecated.
-
+
+ Please note that this changes the method signatures for AR#new, AR#create, AR#create!, AR#update_attributes and AR#update_attributes!. If you have overwritten these methods you should update them accordingly.
+
[Josh Kalderimis]
* default_scope can take a block, lambda, or any other object which responds to `call` for lazy
@@ -290,6 +306,84 @@ IrreversibleMigration exception will be raised when going down.
[Aaron Patterson]
+*Rails 3.0.7 (April 18, 2011)*
+
+* Destroying records via nested attributes works independent of reject_if LH #6006 [Durran Jordan]
+
+* Delegate any? and many? to Model.scoped for consistency [Andrew White]
+
+* Quote the ORDER BY clause in batched finds - fixes #6620 [Andrew White]
+
+* Change exists? so records are not instantiated - fixes #6127. This prevents after_find
+ and after_initialize callbacks being triggered when checking for record existence.
+ [Andrew White]
+
+* Fix performance bug with attribute accessors which only occurred on Ruby 1.8.7, and ensure we
+ cache type-casted values when the column returned from the db contains non-standard chars.
+ [Jon Leighton]
+
+* Fix a performance regression introduced here 86acbf1cc050c8fa8c74a10c735e467fb6fd7df8
+ related to read_attribute method [Stian Grytøyr]
+
+
+*Rails 3.0.6 (April 5, 2011)*
+
+* Un-deprecate reorder method [Sebastian Martinez]
+
+* Extensions are applied when calling +except+ or +only+ on relations.
+ Thanks to Iain Hecker.
+
+* Schemas set in set_table_name are respected by the mysql adapter. LH #5322
+
+* Fixed a bug when empty? was called on a grouped Relation that wasn't loaded.
+ LH #5829
+
+* Reapply extensions when using except and only. Thanks Iain Hecker.
+
+* Binary data is escaped when being inserted to SQLite3 Databases. Thanks
+ Naruse!
+
+
+*Rails 3.0.5 (February 26, 2011)*
+
+* Model.where(:column => 1).where(:column => 2) will always produce an AND
+query.
+
+ [Aaron Patterson]
+
+* Deprecated support for interpolated association conditions in the form of :conditions => 'foo = #{bar}'.
+
+ Instead, you should use a proc, like so:
+
+ Before:
+
+ has_many :things, :conditions => 'foo = #{bar}'
+
+ After:
+
+ has_many :things, :conditions => proc { "foo = #{bar}" }
+
+ Inside the proc, 'self' is the object which is the owner of the association, unless you are
+ eager loading the association, in which case 'self' is the class which the association is within.
+
+ You can have any "normal" conditions inside the proc, so the following will work too:
+
+ has_many :things, :conditions => proc { ["foo = ?", bar] }
+
+ Previously :insert_sql and :delete_sql on has_and_belongs_to_many association allowed you to call
+ 'record' to get the record being inserted or deleted. This is now passed as an argument to
+ the proc.
+
+ [Jon Leighton]
+
+
+*Rails 3.0.4 (February 8, 2011)*
+
+* Added deprecation warning for has_and_belongs_to_many associations where the join table has
+ additional attributes other than the keys. Access to these attributes is removed in 3.1.
+ Please use has_many :through instead. [Jon Leighton]
+
+
*Rails 3.0.3 (November 16, 2010)*
* Support find by class like this: Post.where(:name => Post)
@@ -326,10 +420,12 @@ IrreversibleMigration exception will be raised when going down.
[Aaron Patterson]
+
*Rails 3.0.1 (October 15, 2010)*
* Introduce a fix for CVE-2010-3993
+
*Rails 3.0.0 (August 29, 2010)*
* Changed update_attribute to not run callbacks and update the record directly in the database [Neeraj Singh]
@@ -529,12 +625,12 @@ IrreversibleMigration exception will be raised when going down.
* Add Support for updating deeply nested models from a single form. #1202 [Eloy Duran]
- class Book < ActiveRecord::Base
- has_one :author
- has_many :pages
+ class Book < ActiveRecord::Base
+ has_one :author
+ has_many :pages
- accepts_nested_attributes_for :author, :pages
- end
+ accepts_nested_attributes_for :author, :pages
+ end
* Make after_save callbacks fire only if the record was successfully saved. #1735 [Michael Lovitt]
@@ -954,7 +1050,7 @@ so newlines etc are escaped #10385 [Norbert Crombach]
"foo.bar" => "`foo`.`bar`"
* Complete the assimilation of Sexy Migrations from ErrFree [Chris Wanstrath, PJ Hyett]
- http://errtheblog.com/post/2381
+ http://errtheblog.com/post/2381
* Qualified column names work in hash conditions, like :conditions => { 'comments.created_at' => ... }. #9733 [Jack Danger Canty]
@@ -1070,7 +1166,7 @@ single-table inheritance. #3833, #9886 [Gabriel Gironda, rramdas, François Bea
* Improve performance and functionality of the postgresql adapter. Closes #8049 [roderickvd]
- For more information see: http://dev.rubyonrails.org/ticket/8049
+ For more information see: http://dev.rubyonrails.org/ticket/8049
* Don't clobber includes passed to has_many.count [Jack Danger Canty]
@@ -1580,8 +1676,8 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
* Added support for conditions on Base.exists? #5689 [Josh Peek]. Examples:
assert (Topic.exists?(:author_name => "David"))
- assert (Topic.exists?(:author_name => "Mary", :approved => true))
- assert (Topic.exists?(["parent_id = ?", 1]))
+ assert (Topic.exists?(:author_name => "Mary", :approved => true))
+ assert (Topic.exists?(["parent_id = ?", 1]))
* Schema dumper quotes date :default values. [Dave Thomas]
@@ -2037,8 +2133,8 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
* Added support for conditions on Base.exists? #5689 [Josh Peek]. Examples:
assert (Topic.exists?(:author_name => "David"))
- assert (Topic.exists?(:author_name => "Mary", :approved => true))
- assert (Topic.exists?(["parent_id = ?", 1]))
+ assert (Topic.exists?(:author_name => "Mary", :approved => true))
+ assert (Topic.exists?(["parent_id = ?", 1]))
* Schema dumper quotes date :default values. [Dave Thomas]
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
index a27640eac9..3a89446a83 100644
--- a/activerecord/README.rdoc
+++ b/activerecord/README.rdoc
@@ -219,4 +219,4 @@ API documentation is at
Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
-* https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets
+* https://github.com/rails/rails/issues
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 08fb6bf3c4..9bc44e5163 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1379,7 +1379,7 @@ module ActiveRecord
# [:touch]
# If true, the associated object will be touched (the updated_at/on attributes set to now)
# when this record is either saved or destroyed. If you specify a symbol, that attribute
- # will be updated with the current time instead of the updated_at/on attribute.
+ # will be updated with the current time in addition to the updated_at/on attribute.
# [:inverse_of]
# Specifies the name of the <tt>has_one</tt> or <tt>has_many</tt> association on the associated
# object that is the inverse of this <tt>belongs_to</tt> association. Does not work in
diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb
index 634dee2289..44e2ee141e 100644
--- a/activerecord/lib/active_record/associations/alias_tracker.rb
+++ b/activerecord/lib/active_record/associations/alias_tracker.rb
@@ -50,7 +50,7 @@ module ActiveRecord
end
def pluralize(table_name)
- ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
+ ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name.to_s
end
private
diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb
index 06a414b874..638a2ec72a 100644
--- a/activerecord/lib/active_record/associations/builder/singular_association.rb
+++ b/activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -13,19 +13,32 @@ module ActiveRecord::Associations::Builder
private
+ def define_readers
+ super
+ name = self.name
+
+ model.redefine_method("#{name}_loaded?") do
+ ActiveSupport::Deprecation.warn(
+ "Calling obj.#{name}_loaded? is deprecated. Please use " \
+ "obj.association(:#{name}).loaded? instead."
+ )
+ association(name).loaded?
+ end
+ end
+
def define_constructors
name = self.name
- model.redefine_method("build_#{name}") do |*params|
- association(name).build(*params)
+ model.redefine_method("build_#{name}") do |*params, &block|
+ association(name).build(*params, &block)
end
- model.redefine_method("create_#{name}") do |*params|
- association(name).create(*params)
+ model.redefine_method("create_#{name}") do |*params, &block|
+ association(name).create(*params, &block)
end
- model.redefine_method("create_#{name}!") do |*params|
- association(name).create!(*params)
+ model.redefine_method("create_#{name}!") do |*params, &block|
+ association(name).create!(*params, &block)
end
end
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 6cdec8c487..902ad8cb64 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -4,7 +4,7 @@ module ActiveRecord
module Associations
# = Active Record Association Collection
#
- # AssociationCollection is an abstract class that provides common stuff to
+ # CollectionAssociation is an abstract class that provides common stuff to
# ease the implementation of association proxies that represent
# collections. See the class hierarchy in AssociationProxy.
#
@@ -94,7 +94,13 @@ module ActiveRecord
end
def build(attributes = {}, options = {}, &block)
- build_or_create(:build, attributes, options, &block)
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| build(attr, options, &block) }
+ else
+ add_to_target(build_record(attributes, options)) do |record|
+ yield(record) if block_given?
+ end
+ end
end
def create(attributes = {}, options = {}, &block)
@@ -102,7 +108,16 @@ module ActiveRecord
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
end
- build_or_create(:create, attributes, options, &block)
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| create(attr, options, &block) }
+ else
+ transaction do
+ add_to_target(build_record(attributes, options)) do |record|
+ yield(record) if block_given?
+ insert_record(record)
+ end
+ end
+ end
end
def create!(attrs = {}, options = {}, &block)
@@ -321,15 +336,7 @@ module ActiveRecord
def load_target
if find_target?
- targets = []
-
- begin
- targets = find_target
- rescue ActiveRecord::RecordNotFound
- reset
- end
-
- @target = merge_target_lists(targets, target)
+ @target = merge_target_lists(find_target, target)
end
loaded!
@@ -337,20 +344,18 @@ module ActiveRecord
end
def add_to_target(record)
- transaction do
- callback(:before_add, record)
- yield(record) if block_given?
-
- if options[:uniq] && index = @target.index(record)
- @target[index] = record
- else
- @target << record
- end
+ callback(:before_add, record)
+ yield(record) if block_given?
- callback(:after_add, record)
- set_inverse_instance(record)
+ if options[:uniq] && index = @target.index(record)
+ @target[index] = record
+ else
+ @target << record
end
+ callback(:after_add, record)
+ set_inverse_instance(record)
+
record
end
@@ -374,7 +379,7 @@ module ActiveRecord
if options[:finder_sql]
reflection.klass.find_by_sql(custom_finder_sql)
else
- find(:all)
+ scoped.all
end
records = options[:uniq] ? uniq(records) : records
@@ -382,38 +387,35 @@ module ActiveRecord
records
end
- def merge_target_lists(loaded, existing)
- return loaded if existing.empty?
- return existing if loaded.empty?
-
- loaded.map do |f|
- i = existing.index(f)
- if i
- existing.delete_at(i).tap do |t|
- keys = ["id"] + t.changes.keys + (f.attribute_names - t.attribute_names)
- # FIXME: this call to attributes causes many NoMethodErrors
- attributes = f.attributes
- (attributes.keys - keys).each do |k|
- t.send("#{k}=", attributes[k])
- end
+ # We have some records loaded from the database (persisted) and some that are
+ # in-memory (memory). The same record may be represented in the persisted array
+ # and in the memory array.
+ #
+ # So the task of this method is to merge them according to the following rules:
+ #
+ # * The final array must not have duplicates
+ # * The order of the persisted array is to be preserved
+ # * Any changes made to attributes on objects in the memory array are to be preserved
+ # * Otherwise, attributes should have the value found in the database
+ def merge_target_lists(persisted, memory)
+ return persisted if memory.empty?
+ return memory if persisted.empty?
+
+ persisted.map! do |record|
+ mem_record = memory.delete(record)
+
+ if mem_record
+ (record.attribute_names - mem_record.changes.keys).each do |name|
+ mem_record[name] = record[name]
end
- else
- f
- end
- end + existing
- end
- def build_or_create(method, attributes, options)
- records = Array.wrap(attributes).map do |attrs|
- record = build_record(attrs, options)
-
- add_to_target(record) do
- yield(record) if block_given?
- insert_record(record) if method == :create
+ mem_record
+ else
+ record
end
end
- attributes.is_a?(Array) ? records : records.first
+ persisted + memory
end
# Do the relevant stuff to insert the given record into the association collection.
@@ -421,8 +423,15 @@ module ActiveRecord
raise NotImplementedError
end
+ def create_scope
+ scoped.scope_for_create.stringify_keys
+ end
+
def build_record(attributes, options)
- reflection.build_association(scoped.scope_for_create.merge(attributes), options)
+ record = reflection.build_association(attributes, options)
+ record.assign_attributes(create_scope.except(*record.changed), :without_protection => true)
+ record.assign_attributes(attributes, options)
+ record
end
def delete_or_destroy(records, method)
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 7134dc85c8..2f3a6e71f1 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -10,7 +10,7 @@ module ActiveRecord
reflection.klass.transaction do
if target && target != record
- remove_target!(options[:dependent])
+ remove_target!(options[:dependent]) unless target.destroyed?
end
if record
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index ea4d73d414..68fe0bde76 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -17,20 +17,28 @@ module ActiveRecord
replace(record)
end
- def create(attributes = {}, options = {})
- new_record(:create, attributes, options)
+ def create(attributes = {}, options = {}, &block)
+ build(attributes, options, &block).tap { |record| record.save }
end
- def create!(attributes = {}, options = {})
- build(attributes, options).tap { |record| record.save! }
+ def create!(attributes = {}, options = {}, &block)
+ build(attributes, options, &block).tap { |record| record.save! }
end
- def build(attributes = {}, options = {})
- new_record(:build, attributes, options)
+ def build(attributes = {}, options = {}, &block)
+ record = reflection.build_association(attributes, options)
+ record.assign_attributes(create_scope.except(*record.changed), :without_protection => true)
+ yield(record) if block_given?
+ set_new_record(record)
+ record
end
private
+ def create_scope
+ scoped.scope_for_create.stringify_keys.except(klass.primary_key)
+ end
+
def find_target
scoped.first.tap { |record| set_inverse_instance(record) }
end
@@ -43,13 +51,6 @@ module ActiveRecord
def set_new_record(record)
replace(record)
end
-
- def new_record(method, attributes, options)
- attributes = scoped.scope_for_create.merge(attributes || {})
- record = reflection.send("#{method}_association", attributes, options)
- set_new_record(record)
- record
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index e6ab628719..53c5c3cedf 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -14,7 +14,10 @@ module ActiveRecord
def target_scope
scope = super
chain[1..-1].each do |reflection|
- scope = scope.merge(reflection.klass.scoped)
+ scope = scope.merge(
+ reflection.klass.scoped.with_default_scope.
+ except(:select, :create_with)
+ )
end
scope
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 78318b1be0..cd16b8d3ca 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -482,7 +482,7 @@ module ActiveRecord #:nodoc:
# # Create a single new object
# User.create(:first_name => 'Jamie')
#
- # # Create a single new object using the :admin mass-assignment security scope
+ # # Create a single new object using the :admin mass-assignment security role
# User.create({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
#
# # Create a single new object bypassing mass-assignment security
@@ -767,6 +767,17 @@ module ActiveRecord #:nodoc:
super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
end
+ # Returns an array of column names as strings if it's not
+ # an abstract class and table exists.
+ # Otherwise it returns an empty array.
+ def attribute_names
+ @attribute_names ||= if !abstract_class? && table_exists?
+ column_names
+ else
+ []
+ end
+ end
+
# Set the lookup ancestors for ActiveModel.
def lookup_ancestors #:nodoc:
klass = self
@@ -895,7 +906,7 @@ module ActiveRecord #:nodoc:
# not use the default_scope:
#
# Post.unscoped {
- # limit(10) # Fires "SELECT * FROM posts LIMIT 10"
+ # Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
# }
#
# It is recommended to use block form of unscoped because chaining unscoped with <tt>scope</tt>
@@ -1486,7 +1497,7 @@ MSG
# # Instantiates a single new object
# User.new(:first_name => 'Jamie')
#
- # # Instantiates a single new object using the :admin mass-assignment security scope
+ # # Instantiates a single new object using the :admin mass-assignment security role
# User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
#
# # Instantiates a single new object bypassing mass-assignment security
@@ -1652,17 +1663,16 @@ MSG
return unless new_attributes.is_a?(Hash)
- guard_protected_attributes ||= true
- if guard_protected_attributes
- assign_attributes(new_attributes)
- else
+ if guard_protected_attributes == false
assign_attributes(new_attributes, :without_protection => true)
+ else
+ assign_attributes(new_attributes)
end
end
# Allows you to set all the attributes for a particular mass-assignment
- # security scope by passing in a hash of attributes with keys matching
- # the attribute names (which again matches the column names) and the scope
+ # security role by passing in a hash of attributes with keys matching
+ # the attribute names (which again matches the column names) and the role
# name using the :as option.
#
# To bypass mass-assignment security you can use the :without_protection => true
@@ -1688,13 +1698,15 @@ MSG
# user.name # => "Josh"
# user.is_admin? # => true
def assign_attributes(new_attributes, options = {})
+ return unless new_attributes
+
attributes = new_attributes.stringify_keys
- scope = options[:as] || :default
+ role = options[:as] || :default
multi_parameter_attributes = []
unless options[:without_protection]
- attributes = sanitize_for_mass_assignment(attributes, scope)
+ attributes = sanitize_for_mass_assignment(attributes, role)
end
attributes.each do |k, v|
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 3045e30407..b3eb23bbb3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -6,15 +6,7 @@ module ActiveRecord
# Returns an array of record hashes with the column names as keys and
# column values as values.
def select_all(sql, name = nil, binds = [])
- if supports_statement_cache?
- select(sql, name, binds)
- else
- return select(sql, name) if binds.empty?
- binds = binds.dup
- select sql.gsub('?') {
- quote(*binds.shift.reverse)
- }, name
- end
+ select(sql, name, binds)
end
# Returns a record hash with the column names as keys and column values
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 8af22fe9f5..ac2da73a84 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -184,6 +184,10 @@ module ActiveRecord
QUOTED_FALSE
end
+ def substitute_at(column, index)
+ Arel.sql "\0"
+ end
+
# REFERENTIAL INTEGRITY ====================================
def disable_referential_integrity(&block) #:nodoc:
@@ -292,14 +296,14 @@ module ActiveRecord
binds = binds.dup
# Pretend to support bind parameters
- execute sql.gsub('?') { quote(*binds.shift.reverse) }, name
+ execute sql.gsub("\0") { quote(*binds.shift.reverse) }, name
end
def exec_delete(sql, name, binds)
binds = binds.dup
# Pretend to support bind parameters
- execute sql.gsub('?') { quote(*binds.shift.reverse) }, name
+ execute sql.gsub("\0") { quote(*binds.shift.reverse) }, name
@connection.affected_rows
end
alias :exec_update :exec_delete
@@ -646,7 +650,8 @@ module ActiveRecord
# Returns an array of record hashes with the column names as keys and
# column values as values.
def select(sql, name = nil, binds = [])
- exec_query(sql, name, binds).to_a
+ binds = binds.dup
+ exec_query(sql.gsub("\0") { quote(*binds.shift.reverse) }, name).to_a
end
def exec_query(sql, name = 'SQL', binds = [])
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 37db2be7a9..f4beeceb61 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -39,6 +39,16 @@ module ActiveRecord
# :stopdoc:
class << self
attr_accessor :money_precision
+ def string_to_time(string)
+ return string unless String === string
+
+ case string
+ when 'infinity' then 1.0 / 0.0
+ when '-infinity' then -1.0 / 0.0
+ else
+ super
+ end
+ end
end
# :startdoc:
@@ -349,6 +359,9 @@ module ActiveRecord
return super unless column
case value
+ when Float
+ return super unless value.infinite? && column.type == :datetime
+ "'#{value.to_s.downcase}'"
when Numeric
return super unless column.sql_type == 'money'
# Not truly string input, so doesn't require (or allow) escape string syntax.
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 0e3ed7aac7..4aa6389a04 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -13,6 +13,7 @@ require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/logger'
require 'active_support/ordered_hash'
+require 'active_support/core_ext/module/deprecation'
if defined? ActiveRecord
class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
@@ -28,11 +29,9 @@ class FixturesFileNotFound < StandardError; end
#
# = Fixture formats
#
-# Fixtures come in 3 flavors:
+# Fixtures come in 1 flavor:
#
# 1. YAML fixtures
-# 2. CSV fixtures
-# 3. Single-file fixtures
#
# == YAML fixtures
#
@@ -74,56 +73,6 @@ class FixturesFileNotFound < StandardError; end
# parent_id: 1
# title: Child
#
-# == CSV fixtures
-#
-# Fixtures can also be kept in the Comma Separated Value (CSV) format. Akin to YAML fixtures, CSV fixtures are stored
-# in a single file, but instead end with the <tt>.csv</tt> file extension
-# (Rails example: <tt><your-rails-app>/test/fixtures/web_sites.csv</tt>).
-#
-# The format of this type of fixture file is much more compact than the others, but also a little harder to read by us
-# humans. The first line of the CSV file is a comma-separated list of field names. The rest of the
-# file is then comprised
-# of the actual data (1 per line). Here's an example:
-#
-# id, name, url
-# 1, Ruby On Rails, http://www.rubyonrails.org
-# 2, Google, http://www.google.com
-#
-# Should you have a piece of data with a comma character in it, you can place double quotes around that value. If you
-# need to use a double quote character, you must escape it with another double quote.
-#
-# Another unique attribute of the CSV fixture is that it has *no* fixture name like the other two formats. Instead, the
-# fixture names are automatically generated by deriving the class name of the fixture file and adding an incrementing
-# number to the end. In our example, the 1st fixture would be called "web_site_1" and the 2nd one would be called
-# "web_site_2".
-#
-# Most databases and spreadsheets support exporting to CSV format, so this is a great format for you to choose if you
-# have existing data somewhere already.
-#
-# == Single-file fixtures
-#
-# This type of fixture was the original format for Active Record that has since been deprecated in
-# favor of the YAML and CSV formats.
-# Fixtures for this format are created by placing text files in a sub-directory (with the name of the model)
-# to the directory appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically
-# configured for Rails, so you can just put your files in <tt><your-rails-app>/test/fixtures/<your-model-name>/</tt> --
-# like <tt><your-rails-app>/test/fixtures/web_sites/</tt> for the WebSite model).
-#
-# Each text file placed in this directory represents a "record". Usually these types of fixtures are named without
-# extensions, but if you are on a Windows machine, you might consider adding <tt>.txt</tt> as the extension.
-# Here's what the above example might look like:
-#
-# web_sites/google
-# web_sites/yahoo.txt
-# web_sites/ruby-on-rails
-#
-# The file format of a standard fixture is simple. Each line is a property (or column in db speak) and has the syntax
-# of "name => value". Here's an example of the ruby-on-rails fixture above:
-#
-# id => 1
-# name => Ruby on Rails
-# url => http://www.rubyonrails.org
-#
# = Using fixtures in testcases
#
# Since fixtures are a testing construct, we use them in our unit and functional tests. There are two ways to use the
@@ -176,7 +125,7 @@ class FixturesFileNotFound < StandardError; end
# = Dynamic fixtures with ERB
#
# Some times you don't care about the content of the fixtures as much as you care about the volume. In these cases, you can
-# mix ERB in with your YAML or CSV fixtures to create a bunch of fixtures for load testing, like:
+# mix ERB in with your YAML fixtures to create a bunch of fixtures for load testing, like:
#
# <% for i in 1..1000 %>
# fix_<%= i %>:
@@ -752,6 +701,7 @@ module ActiveRecord
fixtures["#{@class_name.to_s.underscore}_#{i+=1}"] = ActiveRecord::Fixture.new(data, model_class)
end
end
+ deprecate :read_csv_fixture_files
def yaml_file_path
"#{@fixture_path}.yml"
diff --git a/activerecord/lib/active_record/identity_map.rb b/activerecord/lib/active_record/identity_map.rb
index f88ead9ca0..b15b5a8133 100644
--- a/activerecord/lib/active_record/identity_map.rb
+++ b/activerecord/lib/active_record/identity_map.rb
@@ -12,10 +12,36 @@ module ActiveRecord
# In order to enable IdentityMap, set <tt>config.active_record.identity_map = true</tt>
# in your <tt>config/application.rb</tt> file.
#
- # IdentityMap is disabled by default.
+ # IdentityMap is disabled by default and still in development (i.e. use it with care).
+ #
+ # == Associations
+ #
+ # Active Record Identity Map does not track associations yet. For example:
+ #
+ # comment = @post.comments.first
+ # comment.post = nil
+ # @post.comments.include?(comment) #=> true
+ #
+ # Ideally, the example above would return false, removing the comment object from the
+ # post association when the association is nullified. This may cause side effects, as
+ # in the situation below, if Identity Map is enabled:
+ #
+ # Post.has_many :comments, :dependent => :destroy
+ #
+ # comment = @post.comments.first
+ # comment.post = nil
+ # comment.save
+ # Post.destroy(@post.id)
+ #
+ # Without using Identity Map, the code above will destroy the @post object leaving
+ # the comment object intact. However, once we enable Identity Map, the post loaded
+ # by Post.destroy is exactly the same object as the object @post. As the object @post
+ # still has the comment object in @post.comments, once Identity Map is enabled, the
+ # comment object will be accidently removed.
+ #
+ # This inconsistency is meant to be fixed in future Rails releases.
#
module IdentityMap
- extend ActiveSupport::Concern
class << self
def enabled=(flag)
@@ -53,7 +79,7 @@ module ActiveRecord
if record.is_a?(klass)
ActiveSupport::Notifications.instrument("identity.active_record",
- :line => "From Identity Map (id: #{primary_key})",
+ :line => "From Identity Map (id: #{primary_key})",
:name => "#{klass} Loaded",
:connection_id => object_id)
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 9a31675782..cdedcde0eb 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -94,7 +94,7 @@ module ActiveRecord
relation = self.class.unscoped
stmt = relation.where(
- relation.table[self.class.primary_key].eq(quoted_id).and(
+ relation.table[self.class.primary_key].eq(id).and(
relation.table[lock_col].eq(quote_value(previous_lock_value))
)
).arel.compile_update(arel_attributes_values(false, false, attribute_names))
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index b4531ed35f..b9041f44d8 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -146,7 +146,7 @@ module ActiveRecord
# will fail and false will be returned.
#
# When updating model attributes, mass-assignment security protection is respected.
- # If no +:as+ option is supplied then the +:default+ scope will be used.
+ # If no +:as+ option is supplied then the +:default+ role will be used.
# If you want to bypass the protection given by +attr_protected+ and
# +attr_accessible+ then you can do so using the +:without_protection+ option.
def update_attributes(attributes, options = {})
diff --git a/activerecord/lib/active_record/railties/controller_runtime.rb b/activerecord/lib/active_record/railties/controller_runtime.rb
index bc6ca936c0..9e3b3429e4 100644
--- a/activerecord/lib/active_record/railties/controller_runtime.rb
+++ b/activerecord/lib/active_record/railties/controller_runtime.rb
@@ -2,13 +2,21 @@ require 'active_support/core_ext/module/attr_internal'
module ActiveRecord
module Railties
- module ControllerRuntime
+ module ControllerRuntime #:nodoc:
extend ActiveSupport::Concern
protected
attr_internal :db_runtime
+ def process_action(action, *args)
+ # We also need to reset the runtime before each action
+ # because of queries in middleware or in cases we are streaming
+ # and it won't be cleaned up by the method below.
+ ActiveRecord::LogSubscriber.reset_runtime
+ super
+ end
+
def cleanup_view_runtime
if ActiveRecord::Base.connected?
db_rt_before_render = ActiveRecord::LogSubscriber.reset_runtime
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index c6bc040f9f..85ad43b35f 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -203,18 +203,18 @@ db_namespace = namespace :db do
# only files matching "20091231235959_some_name.rb" pattern
if match_data = /^(\d{14})_(.+)\.rb$/.match(file)
status = db_list.delete(match_data[1]) ? 'up' : 'down'
- file_list << [status, match_data[1], match_data[2]]
+ file_list << [status, match_data[1], match_data[2].humanize]
end
end
+ db_list.map! do |version|
+ ['up', version, '********** NO FILE **********']
+ end
# output
puts "\ndatabase: #{config['database']}\n\n"
puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
puts "-" * 50
- file_list.each do |file|
- puts "#{file[0].center(8)} #{file[1].ljust(14)} #{file[2].humanize}"
- end
- db_list.each do |version|
- puts "#{'up'.center(8)} #{version.ljust(14)} *** NO FILE ***"
+ (db_list + file_list).sort_by {|migration| migration[1]}.each do |migration|
+ puts "#{migration[0].center(8)} #{migration[1].ljust(14)} #{migration[2]}"
end
puts
end
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 869eebfa34..0fcae92d51 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -146,7 +146,7 @@ module ActiveRecord
if options.except(:distinct).present?
apply_finder_options(options.except(:distinct)).calculate(operation, column_name, :distinct => options[:distinct])
else
- if eager_loading? || includes_values.present?
+ if eager_loading? || (includes_values.present? && references_eager_loaded_tables?)
construct_relation_for_association_calculations.calculate(operation, column_name, options)
else
perform_calculation(operation, column_name, options)
@@ -161,21 +161,20 @@ module ActiveRecord
def perform_calculation(operation, column_name, options = {})
operation = operation.to_s.downcase
- distinct = nil
+ distinct = options[:distinct]
if operation == "count"
column_name ||= (select_for_count || :all)
unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
distinct = true
- column_name = primary_key if column_name == :all
end
+ column_name = primary_key if column_name == :all && distinct
+
distinct = nil if column_name =~ /\s*DISTINCT\s+/i
end
- distinct = options[:distinct] || distinct
-
if @group_values.any?
execute_grouped_calculation(operation, column_name, distinct)
else
@@ -197,7 +196,7 @@ module ActiveRecord
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
# Postgresql doesn't like ORDER BY when there are no GROUP BY
- relation = except(:order)
+ relation = reorder(nil)
if operation == "count" && (relation.limit_value || relation.offset_value)
# Shortcut when limit is zero.
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 57c9921ea8..32d1cff6c3 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -375,7 +375,12 @@ module ActiveRecord
if loaded?
@records.last
else
- @last ||= reverse_order.limit(1).to_a[0]
+ @last ||=
+ if offset_value || limit_value
+ to_a.last
+ else
+ reverse_order.limit(1).to_a[0]
+ end
end
end
diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb
index 7e77aefb21..c3e976002e 100644
--- a/activerecord/lib/active_record/session_store.rb
+++ b/activerecord/lib/active_record/session_store.rb
@@ -40,7 +40,7 @@ module ActiveRecord
# You must implement these methods:
#
# self.find_by_session_id(session_id)
- # initialize(hash_of_session_id_and_data)
+ # initialize(hash_of_session_id_and_data, options_hash = {})
# attr_reader :session_id
# attr_accessor :data
# save
@@ -83,6 +83,8 @@ module ActiveRecord
cattr_accessor :data_column_name
self.data_column_name = 'data'
+ attr_accessible :session_id, :data, :marshaled_data
+
before_save :marshal_data!
before_save :raise_on_session_data_overflow!
@@ -123,7 +125,7 @@ module ActiveRecord
end
end
- def initialize(attributes = nil)
+ def initialize(attributes = nil, options = {})
@data = nil
super
end
diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb
index 29efbbcb8c..0d47eb3338 100644
--- a/activerecord/lib/active_record/test_case.rb
+++ b/activerecord/lib/active_record/test_case.rb
@@ -13,6 +13,13 @@ module ActiveRecord
ActiveRecord::IdentityMap.clear
end
+ # Backport skip to Ruby 1.8. test/unit doesn't support it, so just
+ # make it a noop.
+ unless instance_methods.map(&:to_s).include?("skip")
+ def skip(message)
+ end
+ end
+
def assert_date_from_db(expected, actual, message = nil)
# SybaseAdapter doesn't have a separate column type just for dates,
# so the time is in the string and incorrectly formatted
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index de36dd20b3..59b6876135 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -32,11 +32,11 @@ module ActiveRecord
module ClassMethods
# Creates an object just like Base.create but calls <tt>save!</tt> instead of +save+
# so an exception is raised if the record is invalid.
- def create!(attributes = nil, &block)
+ def create!(attributes = nil, options = {}, &block)
if attributes.is_a?(Array)
- attributes.collect { |attr| create!(attr, &block) }
+ attributes.collect { |attr| create!(attr, options, &block) }
else
- object = new(attributes)
+ object = new(attributes, options)
yield(object) if block_given?
object.save!
object
diff --git a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb
new file mode 100644
index 0000000000..cd9c1041dc
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb
@@ -0,0 +1,50 @@
+require "cases/helper"
+require 'models/topic'
+
+module ActiveRecord
+ module ConnectionAdapters
+ class Mysql2Adapter
+ class BindParameterTest < ActiveRecord::TestCase
+ fixtures :topics
+
+ def test_update_question_marks
+ str = "foo?bar"
+ x = Topic.find :first
+ x.title = str
+ x.content = str
+ x.save!
+ x.reload
+ assert_equal str, x.title
+ assert_equal str, x.content
+ end
+
+ def test_create_question_marks
+ str = "foo?bar"
+ x = Topic.create!(:title => str, :content => str)
+ x.reload
+ assert_equal str, x.title
+ assert_equal str, x.content
+ end
+
+ def test_update_null_bytes
+ str = "foo\0bar"
+ x = Topic.find :first
+ x.title = str
+ x.content = str
+ x.save!
+ x.reload
+ assert_equal str, x.title
+ assert_equal str, x.content
+ end
+
+ def test_create_null_bytes
+ str = "foo\0bar"
+ x = Topic.create!(:title => str, :content => str)
+ x.reload
+ assert_equal str, x.title
+ assert_equal str, x.content
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index ddcc36c841..b993bf6e90 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -626,4 +626,25 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal "Bob", firm.name
end
+
+ def test_build_with_block
+ client = Client.create(:name => 'Client Company')
+
+ firm = client.build_firm{ |f| f.name = 'Agency Company' }
+ assert_equal 'Agency Company', firm.name
+ end
+
+ def test_create_with_block
+ client = Client.create(:name => 'Client Company')
+
+ firm = client.create_firm{ |f| f.name = 'Agency Company' }
+ assert_equal 'Agency Company', firm.name
+ end
+
+ def test_create_bang_with_block
+ client = Client.create(:name => 'Client Company')
+
+ firm = client.create_firm!{ |f| f.name = 'Agency Company' }
+ assert_equal 'Agency Company', firm.name
+ end
end
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index 39e8a7960a..49d8722aff 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -51,7 +51,9 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
categories = Category.joins(:categorizations).includes([{:posts=>:comments}, :authors])
assert_nothing_raised do
- assert_equal 3, categories.count
+ assert_equal 4, categories.count
+ assert_equal 4, categories.all.count
+ assert_equal 3, categories.count(:distinct => true)
assert_equal 3, categories.all.uniq.size # Must uniq since instantiating with inner joins will get dupes
end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 247decc67b..522ac56d82 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -66,6 +66,63 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 'exotic', bulb.name
end
+ def test_create_from_association_with_nil_values_should_work
+ car = Car.create(:name => 'honda')
+
+ bulb = car.bulbs.new(nil)
+ assert_equal 'defaulty', bulb.name
+
+ bulb = car.bulbs.build(nil)
+ assert_equal 'defaulty', bulb.name
+
+ bulb = car.bulbs.create(nil)
+ assert_equal 'defaulty', bulb.name
+ end
+
+ def test_association_keys_bypass_attribute_protection
+ car = Car.create(:name => 'honda')
+
+ bulb = car.bulbs.new
+ assert_equal car.id, bulb.car_id
+
+ bulb = car.bulbs.new :car_id => car.id + 1
+ assert_equal car.id, bulb.car_id
+
+ bulb = car.bulbs.build
+ assert_equal car.id, bulb.car_id
+
+ bulb = car.bulbs.build :car_id => car.id + 1
+ assert_equal car.id, bulb.car_id
+
+ bulb = car.bulbs.create
+ assert_equal car.id, bulb.car_id
+
+ bulb = car.bulbs.create :car_id => car.id + 1
+ assert_equal car.id, bulb.car_id
+ end
+
+ def test_association_conditions_bypass_attribute_protection
+ car = Car.create(:name => 'honda')
+
+ bulb = car.frickinawesome_bulbs.new
+ assert_equal true, bulb.frickinawesome?
+
+ bulb = car.frickinawesome_bulbs.new(:frickinawesome => false)
+ assert_equal true, bulb.frickinawesome?
+
+ bulb = car.frickinawesome_bulbs.build
+ assert_equal true, bulb.frickinawesome?
+
+ bulb = car.frickinawesome_bulbs.build(:frickinawesome => false)
+ assert_equal true, bulb.frickinawesome?
+
+ bulb = car.frickinawesome_bulbs.create
+ assert_equal true, bulb.frickinawesome?
+
+ bulb = car.frickinawesome_bulbs.create(:frickinawesome => false)
+ assert_equal true, bulb.frickinawesome?
+ end
+
# When creating objects on the association, we must not do it within a scope (even though it
# would be convenient), because this would cause that scope to be applied to any callbacks etc.
def test_build_and_create_should_not_happen_within_scope
@@ -1388,4 +1445,27 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_not_equal target.object_id, ary.object_id
end
+
+ def test_merging_with_custom_attribute_writer
+ bulb = Bulb.new(:color => "red")
+ assert_equal "RED!", bulb.color
+
+ car = Car.create!
+ car.bulbs << bulb
+
+ assert_equal "RED!", car.bulbs.to_a.first.color
+ end
+
+ def test_new_is_called_with_attributes_and_options
+ car = Car.create(:name => 'honda')
+
+ bulb = car.bulbs.build
+ assert_equal Bulb, bulb.class
+
+ bulb = car.bulbs.build(:bulb_type => :custom)
+ assert_equal Bulb, bulb.class
+
+ bulb = car.bulbs.build({ :bulb_type => :custom }, :as => :admin)
+ assert_equal CustomBulb, bulb.class
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index f3c96ccbe6..f3bf5baa95 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -4,6 +4,7 @@ require 'models/project'
require 'models/company'
require 'models/ship'
require 'models/pirate'
+require 'models/car'
require 'models/bulb'
class HasOneAssociationsTest < ActiveRecord::TestCase
@@ -95,6 +96,15 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_nil Account.find(old_account_id).firm_id
end
+ def test_natural_assignment_to_nil_after_destroy
+ firm = companies(:rails_core)
+ old_account_id = firm.account.id
+ firm.account.destroy
+ firm.account = nil
+ assert_nil companies(:rails_core).account
+ assert_raise(ActiveRecord::RecordNotFound) { Account.find(old_account_id) }
+ end
+
def test_association_change_calls_delete
companies(:first_firm).deletable_account = Account.new(:credit_limit => 5)
assert_equal [], Account.destroyed_account_ids[companies(:first_firm).id]
@@ -359,4 +369,82 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal pirate.id, ships(:black_pearl).reload.pirate_id
assert_nil new_ship.pirate_id
end
+
+ def test_deprecated_association_loaded
+ firm = companies(:first_firm)
+ firm.association(:account).stubs(:loaded?).returns(stub)
+
+ assert_deprecated do
+ assert_equal firm.association(:account).loaded?, firm.account_loaded?
+ end
+ end
+
+ def test_association_keys_bypass_attribute_protection
+ car = Car.create(:name => 'honda')
+
+ bulb = car.build_bulb
+ assert_equal car.id, bulb.car_id
+
+ bulb = car.build_bulb :car_id => car.id + 1
+ assert_equal car.id, bulb.car_id
+
+ bulb = car.create_bulb
+ assert_equal car.id, bulb.car_id
+
+ bulb = car.create_bulb :car_id => car.id + 1
+ assert_equal car.id, bulb.car_id
+ end
+
+ def test_association_conditions_bypass_attribute_protection
+ car = Car.create(:name => 'honda')
+
+ bulb = car.build_frickinawesome_bulb
+ assert_equal true, bulb.frickinawesome?
+
+ bulb = car.build_frickinawesome_bulb(:frickinawesome => false)
+ assert_equal true, bulb.frickinawesome?
+
+ bulb = car.create_frickinawesome_bulb
+ assert_equal true, bulb.frickinawesome?
+
+ bulb = car.create_frickinawesome_bulb(:frickinawesome => false)
+ assert_equal true, bulb.frickinawesome?
+ end
+
+ def test_new_is_called_with_attributes_and_options
+ car = Car.create(:name => 'honda')
+
+ bulb = car.build_bulb
+ assert_equal Bulb, bulb.class
+
+ bulb = car.build_bulb
+ assert_equal Bulb, bulb.class
+
+ bulb = car.build_bulb(:bulb_type => :custom)
+ assert_equal Bulb, bulb.class
+
+ bulb = car.build_bulb({ :bulb_type => :custom }, :as => :admin)
+ assert_equal CustomBulb, bulb.class
+ end
+
+ def test_build_with_block
+ car = Car.create(:name => 'honda')
+
+ bulb = car.build_bulb{ |b| b.color = 'Red' }
+ assert_equal 'RED!', bulb.color
+ end
+
+ def test_create_with_block
+ car = Car.create(:name => 'honda')
+
+ bulb = car.create_bulb{ |b| b.color = 'Red' }
+ assert_equal 'RED!', bulb.color
+ end
+
+ def test_create_bang_with_block
+ car = Car.create(:name => 'honda')
+
+ bulb = car.create_bulb!{ |b| b.color = 'Red' }
+ assert_equal 'RED!', bulb.color
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index 968025ade8..2503349c08 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -310,4 +310,8 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
assert_equal dashboard, minivan.dashboard
assert_equal dashboard, minivan.speedometer.dashboard
end
+
+ def test_has_one_through_with_custom_select_on_join_model_default_scope
+ assert_equal clubs(:boring_club), members(:groucho).selected_club
+ end
end
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index 124693f7c9..e5e9ca6131 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -2,6 +2,7 @@ require "cases/helper"
require 'models/post'
require 'models/comment'
require 'models/author'
+require 'models/essay'
require 'models/category'
require 'models/categorization'
require 'models/person'
@@ -9,7 +10,7 @@ require 'models/tagging'
require 'models/tag'
class InnerJoinAssociationTest < ActiveRecord::TestCase
- fixtures :authors, :posts, :comments, :categories, :categories_posts, :categorizations,
+ fixtures :authors, :essays, :posts, :comments, :categories, :categories_posts, :categorizations,
:taggings, :tags
def test_construct_finder_sql_applies_aliases_tables_on_association_conditions
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 49a1c117bc..8e23ab78be 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -13,6 +13,9 @@ require 'models/vertex'
require 'models/edge'
require 'models/book'
require 'models/citation'
+require 'models/aircraft'
+require 'models/engine'
+require 'models/car'
class AssociationsJoinModelTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false unless supports_savepoints?
@@ -704,6 +707,15 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert_equal [9, 10, new_comment.id], authors(:david).sti_post_comments.map(&:id).sort
end
+ def test_has_many_with_pluralize_table_names_false
+ engine = Engine.create(:car_id => 1)
+ Aircraft.pluralize_table_names = false
+ aircraft = Aircraft.create!(:name => "Airbus 380", :id => 1)
+ assert_equal aircraft.engines, [engine]
+ ensure
+ ActiveRecord::Base.pluralize_table_names = true
+ end
+
private
# create dynamic Post models to allow different dependency options
def find_post_with_dependency(post_id, association, association_name, dependency)
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 04f628a398..49d82ba2df 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -85,7 +85,7 @@ class AssociationsTest < ActiveRecord::TestCase
def test_should_construct_new_finder_sql_after_create
person = Person.new :first_name => 'clark'
- assert_equal [], person.readers.find(:all)
+ assert_equal [], person.readers.all
person.save!
reader = Reader.create! :person => person, :post => Post.new(:title => "foo", :body => "bar")
assert person.readers.find(reader.id)
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 1775ba9999..bfb66f07da 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -45,6 +45,10 @@ class ReadonlyTitlePost < Post
attr_readonly :title
end
+class ProtectedTitlePost < Post
+ attr_protected :title
+end
+
class Weird < ActiveRecord::Base; end
class Boolean < ActiveRecord::Base; end
@@ -105,7 +109,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_select_symbol
topic_ids = Topic.select(:id).map(&:id).sort
- assert_equal Topic.find(:all).map(&:id).sort, topic_ids
+ assert_equal Topic.all.map(&:id).sort, topic_ids
end
def test_table_exists
@@ -491,8 +495,9 @@ class BasicsTest < ActiveRecord::TestCase
def test_attributes_guard_protected_attributes_is_deprecated
attributes = { "title" => "An amazing title" }
- topic = Topic.new
- assert_deprecated { topic.send(:attributes=, attributes, false) }
+ post = ProtectedTitlePost.new
+ assert_deprecated { post.send(:attributes=, attributes, false) }
+ assert_equal "An amazing title", post.title
end
def test_multiparameter_attributes_on_date
@@ -1785,4 +1790,17 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal expected.attributes, actual.attributes
end
+
+ def test_attribute_names
+ assert_equal ["id", "type", "ruby_type", "firm_id", "firm_name", "name", "client_of", "rating", "account_id"],
+ Company.attribute_names
+ end
+
+ def test_attribute_names_on_table_not_exists
+ assert_equal [], NonExistentTable.attribute_names
+ end
+
+ def test_attribtue_names_on_abstract_class
+ assert_equal [], AbstractCompany.attribute_names
+ end
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 654c4c9010..56f6d795b6 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -319,6 +319,17 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal 4, Account.count(:distinct => true, :include => :firm, :select => :credit_limit)
end
+ def test_should_not_perform_joined_include_by_default
+ assert_equal Account.count, Account.includes(:firm).count
+ queries = assert_sql { Account.includes(:firm).count }
+ assert_no_match(/join/i, queries.last)
+ end
+
+ def test_should_perform_joined_include_when_referencing_included_tables
+ joined_count = Account.includes(:firm).where(:companies => {:name => '37signals'}).count
+ assert_equal 1, joined_count
+ end
+
def test_should_count_scoped_select
Account.update_all("credit_limit = NULL")
assert_equal 0, Account.scoped(:select => "credit_limit").count
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index be4ba18555..4e75eafe3d 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -683,6 +683,27 @@ class FinderTest < ActiveRecord::TestCase
assert_nil Topic.find_last_by_title_and_author_name(topic.title, "Anonymous")
end
+ def test_find_last_with_limit_gives_same_result_when_loaded_and_unloaded
+ scope = Topic.limit(2)
+ unloaded_last = scope.last
+ loaded_last = scope.all.last
+ assert_equal loaded_last, unloaded_last
+ end
+
+ def test_find_last_with_limit_and_offset_gives_same_result_when_loaded_and_unloaded
+ scope = Topic.offset(2).limit(2)
+ unloaded_last = scope.last
+ loaded_last = scope.all.last
+ assert_equal loaded_last, unloaded_last
+ end
+
+ def test_find_last_with_offset_gives_same_result_when_loaded_and_unloaded
+ scope = Topic.offset(3)
+ unloaded_last = scope.last
+ loaded_last = scope.all.last
+ assert_equal loaded_last, unloaded_last
+ end
+
def test_find_all_by_one_attribute
topics = Topic.find_all_by_content("Have a nice day")
assert_equal 2, topics.size
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 2bf192e2c6..b0bd9c5763 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -175,7 +175,9 @@ class FixturesTest < ActiveRecord::TestCase
end
def test_empty_csv_fixtures
- assert_not_nil ActiveRecord::Fixtures.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/csv/accounts")
+ assert_deprecated do
+ assert_not_nil ActiveRecord::Fixtures.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/csv/accounts")
+ end
end
def test_omap_fixtures
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 636a709924..61baa55027 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -5,6 +5,7 @@ require 'models/job'
require 'models/reader'
require 'models/legacy_thing'
require 'models/reference'
+require 'models/string_key_object'
class LockWithoutDefault < ActiveRecord::Base; end
@@ -18,7 +19,40 @@ class ReadonlyFirstNamePerson < Person
end
class OptimisticLockingTest < ActiveRecord::TestCase
- fixtures :people, :legacy_things, :references
+ fixtures :people, :legacy_things, :references, :string_key_objects
+
+ def test_non_integer_lock_existing
+ s1 = StringKeyObject.find("record1")
+ s2 = StringKeyObject.find("record1")
+ assert_equal 0, s1.lock_version
+ assert_equal 0, s2.lock_version
+
+ s1.name = 'updated record'
+ s1.save!
+ assert_equal 1, s1.lock_version
+ assert_equal 0, s2.lock_version
+
+ s2.name = 'doubly updated record'
+ assert_raise(ActiveRecord::StaleObjectError) { s2.save! }
+ end
+
+ def test_non_integer_lock_destroy
+ s1 = StringKeyObject.find("record1")
+ s2 = StringKeyObject.find("record1")
+ assert_equal 0, s1.lock_version
+ assert_equal 0, s2.lock_version
+
+ s1.name = 'updated record'
+ s1.save!
+ assert_equal 1, s1.lock_version
+ assert_equal 0, s2.lock_version
+ assert_raise(ActiveRecord::StaleObjectError) { s2.destroy }
+
+ assert s1.destroy
+ assert s1.frozen?
+ assert s1.destroyed?
+ assert_raises(ActiveRecord::RecordNotFound) { StringKeyObject.find("record1") }
+ end
def test_lock_existing
p1 = Person.find(1)
diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb
index fbbae99e8b..765033852d 100644
--- a/activerecord/test/cases/mass_assignment_security_test.rb
+++ b/activerecord/test/cases/mass_assignment_security_test.rb
@@ -87,7 +87,11 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
end
end
- def test_assign_attributes_uses_default_scope_when_no_scope_is_provided
+ def test_mass_assigning_does_not_choke_on_nil
+ Firm.new.assign_attributes(nil)
+ end
+
+ def test_assign_attributes_uses_default_role_when_no_role_is_provided
p = LoosePerson.new
p.assign_attributes(attributes_hash)
@@ -101,28 +105,28 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
assert_all_attributes(p)
end
- def test_assign_attributes_with_default_scope_and_attr_protected_attributes
+ def test_assign_attributes_with_default_role_and_attr_protected_attributes
p = LoosePerson.new
p.assign_attributes(attributes_hash, :as => :default)
assert_default_attributes(p)
end
- def test_assign_attributes_with_admin_scope_and_attr_protected_attributes
+ def test_assign_attributes_with_admin_role_and_attr_protected_attributes
p = LoosePerson.new
p.assign_attributes(attributes_hash, :as => :admin)
assert_admin_attributes(p)
end
- def test_assign_attributes_with_default_scope_and_attr_accessible_attributes
+ def test_assign_attributes_with_default_role_and_attr_accessible_attributes
p = TightPerson.new
p.assign_attributes(attributes_hash, :as => :default)
assert_default_attributes(p)
end
- def test_assign_attributes_with_admin_scope_and_attr_accessible_attributes
+ def test_assign_attributes_with_admin_role_and_attr_accessible_attributes
p = TightPerson.new
p.assign_attributes(attributes_hash, :as => :admin)
@@ -153,30 +157,42 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
assert_default_attributes(p, true)
end
- def test_new_with_admin_scope_with_attr_accessible_attributes
+ def test_new_with_admin_role_with_attr_accessible_attributes
p = TightPerson.new(attributes_hash, :as => :admin)
assert_admin_attributes(p)
end
- def test_new_with_admin_scope_with_attr_protected_attributes
+ def test_new_with_admin_role_with_attr_protected_attributes
p = LoosePerson.new(attributes_hash, :as => :admin)
assert_admin_attributes(p)
end
- def test_create_with_admin_scope_with_attr_accessible_attributes
+ def test_create_with_admin_role_with_attr_accessible_attributes
p = TightPerson.create(attributes_hash, :as => :admin)
assert_admin_attributes(p, true)
end
- def test_create_with_admin_scope_with_attr_protected_attributes
+ def test_create_with_admin_role_with_attr_protected_attributes
p = LoosePerson.create(attributes_hash, :as => :admin)
assert_admin_attributes(p, true)
end
+ def test_create_with_bang_with_admin_role_with_attr_accessible_attributes
+ p = TightPerson.create!(attributes_hash, :as => :admin)
+
+ assert_admin_attributes(p, true)
+ end
+
+ def test_create_with_bang_with_admin_role_with_attr_protected_attributes
+ p = LoosePerson.create!(attributes_hash, :as => :admin)
+
+ assert_admin_attributes(p, true)
+ end
+
def test_new_with_without_protection_with_attr_accessible_attributes
p = TightPerson.new(attributes_hash, :without_protection => true)
@@ -201,6 +217,18 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
assert_all_attributes(p)
end
+ def test_create_with_bang_with_without_protection_with_attr_accessible_attributes
+ p = TightPerson.create!(attributes_hash, :without_protection => true)
+
+ assert_all_attributes(p)
+ end
+
+ def test_create_with_bang_with_without_protection_with_attr_protected_attributes
+ p = LoosePerson.create!(attributes_hash, :without_protection => true)
+
+ assert_all_attributes(p)
+ end
+
def test_protection_against_class_attribute_writers
[:logger, :configurations, :primary_key_prefix_type, :table_name_prefix, :table_name_suffix, :pluralize_table_names,
:default_timezone, :schema_format, :lock_optimistically, :record_timestamps].each do |method|
@@ -230,12 +258,12 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend)
end
- def test_has_one_build_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_build_with_admin_role_with_attr_protected_attributes
best_friend = @person.build_best_friend(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
- def test_has_one_build_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_build_with_admin_role_with_attr_accessible_attributes
best_friend = @person.build_best_friend(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
@@ -257,12 +285,12 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_create_with_admin_role_with_attr_protected_attributes
best_friend = @person.create_best_friend(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_create_with_admin_role_with_attr_accessible_attributes
best_friend = @person.create_best_friend(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
@@ -284,12 +312,12 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes
best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes
best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
@@ -318,12 +346,12 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend)
end
- def test_has_one_build_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_build_with_admin_role_with_attr_protected_attributes
best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
- def test_has_one_build_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_build_with_admin_role_with_attr_accessible_attributes
best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
@@ -345,12 +373,12 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_create_with_admin_role_with_attr_protected_attributes
best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_create_with_admin_role_with_attr_accessible_attributes
best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
@@ -372,12 +400,12 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes
best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes
best_friend = @person.create_best_friend!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
@@ -406,12 +434,12 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend)
end
- def test_has_one_build_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_build_with_admin_role_with_attr_protected_attributes
best_friend = @person.best_friends.build(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
- def test_has_one_build_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_build_with_admin_role_with_attr_accessible_attributes
best_friend = @person.best_friends.build(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend)
end
@@ -433,12 +461,12 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_create_with_admin_role_with_attr_protected_attributes
best_friend = @person.best_friends.create(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_create_with_admin_role_with_attr_accessible_attributes
best_friend = @person.best_friends.create(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
@@ -460,12 +488,12 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase
assert_default_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes
+ def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes
best_friend = @person.best_friends.create!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
- def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes
+ def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes
best_friend = @person.best_friends.create!(attributes_hash, :as => :admin)
assert_admin_attributes(best_friend, true)
end
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index 7f0f007a70..a0cb5dbdc5 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -68,7 +68,7 @@ class MethodScopingTest < ActiveRecord::TestCase
def test_scoped_find_all
Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
- assert_equal [developers(:david)], Developer.find(:all)
+ assert_equal [developers(:david)], Developer.all
end
end
@@ -235,23 +235,23 @@ class MethodScopingTest < ActiveRecord::TestCase
def test_immutable_scope
options = { :conditions => "name = 'David'" }
Developer.send(:with_scope, :find => options) do
- assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ assert_equal %w(David), Developer.all.map(&:name)
options[:conditions] = "name != 'David'"
- assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ assert_equal %w(David), Developer.all.map(&:name)
end
scope = { :find => { :conditions => "name = 'David'" }}
Developer.send(:with_scope, scope) do
- assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ assert_equal %w(David), Developer.all.map(&:name)
scope[:find][:conditions] = "name != 'David'"
- assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ assert_equal %w(David), Developer.all.map(&:name)
end
end
def test_scoped_with_duck_typing
scoping = Struct.new(:current_scope).new(:find => { :conditions => ["name = ?", 'David'] })
Developer.send(:with_scope, scoping) do
- assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ assert_equal %w(David), Developer.all.map(&:name)
end
end
@@ -432,7 +432,7 @@ class NestedScopingTest < ActiveRecord::TestCase
def test_merged_scoped_find_combines_and_sanitizes_conditions
Developer.send(:with_scope, :find => { :conditions => ["name = ?", 'David'] }) do
Developer.send(:with_scope, :find => { :conditions => ['salary > ?', 9000] }) do
- assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ assert_equal %w(David), Developer.all.map(&:name)
end
end
end
@@ -487,9 +487,9 @@ class NestedScopingTest < ActiveRecord::TestCase
options2 = { :conditions => "name = 'David'" }
Developer.send(:with_scope, :find => options1) do
Developer.send(:with_exclusive_scope, :find => options2) do
- assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ assert_equal %w(David), Developer.all.map(&:name)
options1[:conditions] = options2[:conditions] = nil
- assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ assert_equal %w(David), Developer.all.map(&:name)
end
end
end
@@ -499,9 +499,9 @@ class NestedScopingTest < ActiveRecord::TestCase
options2 = { :conditions => "salary > 10000" }
Developer.send(:with_scope, :find => options1) do
Developer.send(:with_scope, :find => options2) do
- assert_equal %w(Jamis), Developer.find(:all).map { |d| d.name }
+ assert_equal %w(Jamis), Developer.all.map(&:name)
options1[:conditions] = options2[:conditions] = nil
- assert_equal %w(Jamis), Developer.find(:all).map { |d| d.name }
+ assert_equal %w(Jamis), Developer.all.map(&:name)
end
end
end
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index 8fd1fc2577..34188e4915 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -462,7 +462,7 @@ class NamedScopeTest < ActiveRecord::TestCase
[:destroy_all, :reset, :delete_all].each do |method|
before = post.comments.containing_the_letter_e
post.association(:comments).send(method)
- assert before.object_id != post.comments.containing_the_letter_e.object_id, "AssociationCollection##{method} should reset the named scopes cache"
+ assert before.object_id != post.comments.containing_the_letter_e.object_id, "CollectionAssociation##{method} should reset the named scopes cache"
end
end
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index b066575af8..57d1441128 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -336,6 +336,10 @@ class PersistencesTest < ActiveRecord::TestCase
assert !Topic.find(1).approved?
end
+ def test_update_attribute_does_not_choke_on_nil
+ assert Topic.find(1).update_attributes(nil)
+ end
+
def test_update_attribute_for_readonly_attribute
minivan = Minivan.find('m1')
assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_attribute(:color, 'black') }
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index 864b3d4846..c215602567 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -462,4 +462,8 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert DeveloperCalledJamis.unscoped.poor.include?(developers(:david).becomes(DeveloperCalledJamis))
assert_equal 10, DeveloperCalledJamis.unscoped.poor.length
end
+
+ def test_default_scope_order_ignored_by_aggregations
+ assert_equal DeveloperOrderedBySalary.all.count, DeveloperOrderedBySalary.count
+ end
end
diff --git a/activerecord/test/cases/session_store/session_test.rb b/activerecord/test/cases/session_store/session_test.rb
index cee5ddd003..669c0b7b4d 100644
--- a/activerecord/test/cases/session_store/session_test.rb
+++ b/activerecord/test/cases/session_store/session_test.rb
@@ -21,6 +21,12 @@ module ActiveRecord
assert_equal 'sessions', Session.table_name
end
+ def test_accessible_attributes
+ assert Session.accessible_attributes.include?(:session_id)
+ assert Session.accessible_attributes.include?(:data)
+ assert Session.accessible_attributes.include?(:marshaled_data)
+ end
+
def test_create_table!
assert !Session.table_exists?
Session.create_table!
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index ceb1452afd..22d4cac422 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -14,6 +14,32 @@ class TimestampTest < ActiveRecord::TestCase
@previously_updated_at = @developer.updated_at
end
+ def test_load_infinity_and_beyond
+ unless current_adapter?(:PostgreSQLAdapter)
+ return skip("only tested on postgresql")
+ end
+
+ d = Developer.find_by_sql("select 'infinity'::timestamp as updated_at")
+ assert d.first.updated_at.infinite?, 'timestamp should be infinite'
+
+ d = Developer.find_by_sql("select '-infinity'::timestamp as updated_at")
+ time = d.first.updated_at
+ assert time.infinite?, 'timestamp should be infinite'
+ assert_operator time, :<, 0
+ end
+
+ def test_save_infinity_and_beyond
+ unless current_adapter?(:PostgreSQLAdapter)
+ return skip("only tested on postgresql")
+ end
+
+ d = Developer.create!(:name => 'aaron', :updated_at => 1.0 / 0.0)
+ assert_equal(1.0 / 0.0, d.updated_at)
+
+ d = Developer.create!(:name => 'aaron', :updated_at => -1.0 / 0.0)
+ assert_equal(-1.0 / 0.0, d.updated_at)
+ end
+
def test_saving_a_changed_record_updates_its_timestamp
@developer.name = "Jack Bauer"
@developer.save!
diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb
index a6074b23e7..756c8a32eb 100644
--- a/activerecord/test/cases/xml_serialization_test.rb
+++ b/activerecord/test/cases/xml_serialization_test.rb
@@ -143,10 +143,7 @@ class NilXmlSerializationTest < ActiveRecord::TestCase
end
def test_should_serialize_yaml
- assert %r{<preferences(.*)></preferences>}.match(@xml)
- attributes = $1
- assert_match %r{type="yaml"}, attributes
- assert_match %r{nil="true"}, attributes
+ assert_match %r{<preferences nil=\"true\"></preferences>}, @xml
end
end
diff --git a/activerecord/test/fixtures/all/people.csv b/activerecord/test/fixtures/all/people.yml
index e69de29bb2..e69de29bb2 100644
--- a/activerecord/test/fixtures/all/people.csv
+++ b/activerecord/test/fixtures/all/people.yml
diff --git a/activerecord/test/fixtures/memberships.yml b/activerecord/test/fixtures/memberships.yml
index 60eb641054..a5d52bd438 100644
--- a/activerecord/test/fixtures/memberships.yml
+++ b/activerecord/test/fixtures/memberships.yml
@@ -25,3 +25,10 @@ blarpy_winkup_crazy_club:
member_id: 3
favourite: false
type: CurrentMembership
+
+selected_membership_of_boring_club:
+ joined_on: <%= 3.weeks.ago.to_s(:db) %>
+ club: boring_club
+ member_id: 1
+ favourite: false
+ type: SelectedMembership
diff --git a/activerecord/test/fixtures/string_key_objects.yml b/activerecord/test/fixtures/string_key_objects.yml
new file mode 100644
index 0000000000..fa1299915b
--- /dev/null
+++ b/activerecord/test/fixtures/string_key_objects.yml
@@ -0,0 +1,7 @@
+first:
+ id: record1
+ name: first record
+
+second:
+ id: record2
+ name: second record
diff --git a/activerecord/test/models/aircraft.rb b/activerecord/test/models/aircraft.rb
new file mode 100644
index 0000000000..0c47aab539
--- /dev/null
+++ b/activerecord/test/models/aircraft.rb
@@ -0,0 +1,3 @@
+class Aircraft < ActiveRecord::Base
+ has_many :engines, :foreign_key => "car_id"
+end
diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb
index c68d008c26..0dcc8d5970 100644
--- a/activerecord/test/models/bulb.rb
+++ b/activerecord/test/models/bulb.rb
@@ -2,6 +2,8 @@ class Bulb < ActiveRecord::Base
default_scope where(:name => 'defaulty')
belongs_to :car
+ attr_protected :car_id, :frickinawesome
+
attr_reader :scope_after_initialize
after_initialize :record_scope_after_initialize
@@ -9,4 +11,21 @@ class Bulb < ActiveRecord::Base
@scope_after_initialize = self.class.scoped
end
+ def color=(color)
+ self[:color] = color.upcase + "!"
+ end
+
+ def self.new(attributes = {}, options = {}, &block)
+ bulb_type = (attributes || {}).delete(:bulb_type)
+
+ if options && options[:as] == :admin && bulb_type.present?
+ bulb_class = "#{bulb_type.to_s.camelize}Bulb".constantize
+ bulb_class.new(attributes, options, &block)
+ else
+ super
+ end
+ end
end
+
+class CustomBulb < Bulb
+end \ No newline at end of file
diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb
index b036f0f5c9..76f20b1061 100644
--- a/activerecord/test/models/car.rb
+++ b/activerecord/test/models/car.rb
@@ -2,6 +2,11 @@ class Car < ActiveRecord::Base
has_many :bulbs
has_many :foo_bulbs, :class_name => "Bulb", :conditions => { :name => 'foo' }
+ has_many :frickinawesome_bulbs, :class_name => "Bulb", :conditions => { :frickinawesome => true }
+
+ has_one :bulb
+ has_one :frickinawesome_bulb, :class_name => "Bulb", :conditions => { :frickinawesome => true }
+
has_many :tyres
has_many :engines
has_many :wheels, :as => :wheelable
diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb
index 991e0e051f..11a0f4ff63 100644
--- a/activerecord/test/models/member.rb
+++ b/activerecord/test/models/member.rb
@@ -1,8 +1,10 @@
class Member < ActiveRecord::Base
has_one :current_membership
+ has_one :selected_membership
has_one :membership
has_many :fellow_members, :through => :club, :source => :members
has_one :club, :through => :current_membership
+ has_one :selected_club, :through => :selected_membership, :source => :club
has_one :favourite_club, :through => :membership, :conditions => ["memberships.favourite = ?", true], :source => :club
has_one :hairy_club, :through => :membership, :conditions => {:clubs => {:name => "Moustache and Eyebrow Fancier Club"}}, :source => :club
has_one :sponsor, :as => :sponsorable
diff --git a/activerecord/test/models/membership.rb b/activerecord/test/models/membership.rb
index 905f948c37..bcbb7e42c5 100644
--- a/activerecord/test/models/membership.rb
+++ b/activerecord/test/models/membership.rb
@@ -7,3 +7,9 @@ class CurrentMembership < Membership
belongs_to :member
belongs_to :club
end
+
+class SelectedMembership < Membership
+ def self.default_scope
+ select("'1' as foo")
+ end
+end
diff --git a/activerecord/test/models/string_key_object.rb b/activerecord/test/models/string_key_object.rb
new file mode 100644
index 0000000000..f8d4c6e0e4
--- /dev/null
+++ b/activerecord/test/models/string_key_object.rb
@@ -0,0 +1,3 @@
+class StringKeyObject < ActiveRecord::Base
+ set_primary_key :id
+end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 9479242e4f..4fe311b441 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -40,6 +40,10 @@ ActiveRecord::Schema.define do
t.references :account
end
+ create_table :aircraft, :force => true do |t|
+ t.string :name
+ end
+
create_table :audit_logs, :force => true do |t|
t.column :message, :string, :null=>false
t.column :developer_id, :integer, :null=>false
@@ -89,6 +93,8 @@ ActiveRecord::Schema.define do
create_table :bulbs, :force => true do |t|
t.integer :car_id
t.string :name
+ t.boolean :frickinawesome
+ t.string :color
end
create_table "CamelCase", :force => true do |t|
@@ -543,6 +549,12 @@ ActiveRecord::Schema.define do
t.string :sponsorable_type
end
+ create_table :string_key_objects, :id => false, :primary_key => :id, :force => true do |t|
+ t.string :id
+ t.string :name
+ t.integer :lock_version, :null => false, :default => 0
+ end
+
create_table :students, :force => true do |t|
t.string :name
end