diff options
Diffstat (limited to 'activerecord')
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 |