aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG4
-rw-r--r--activerecord/README351
-rw-r--r--activerecord/README.rdoc222
-rw-r--r--activerecord/Rakefile32
-rw-r--r--activerecord/activerecord.gemspec8
-rw-r--r--activerecord/examples/performance.rb2
-rw-r--r--activerecord/install.rb30
-rw-r--r--activerecord/lib/active_record/aggregations.rb115
-rw-r--r--activerecord/lib/active_record/association_preload.rb22
-rw-r--r--activerecord/lib/active_record/associations.rb521
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb41
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb35
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb7
-rw-r--r--activerecord/lib/active_record/associations/through_association_scope.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb3
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb4
-rw-r--r--activerecord/lib/active_record/autosave_association.rb38
-rw-r--r--activerecord/lib/active_record/base.rb257
-rw-r--r--activerecord/lib/active_record/callbacks.rb74
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb44
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb17
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb639
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb112
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb31
-rw-r--r--activerecord/lib/active_record/dynamic_finder_match.rb8
-rw-r--r--activerecord/lib/active_record/errors.rb14
-rw-r--r--activerecord/lib/active_record/fixtures.rb61
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb1
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb20
-rw-r--r--activerecord/lib/active_record/migration.rb2
-rw-r--r--activerecord/lib/active_record/named_scope.rb25
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb4
-rw-r--r--activerecord/lib/active_record/observer.rb4
-rw-r--r--activerecord/lib/active_record/persistence.rb77
-rw-r--r--activerecord/lib/active_record/railtie.rb19
-rw-r--r--activerecord/lib/active_record/railties/controller_runtime.rb4
-rw-r--r--activerecord/lib/active_record/railties/databases.rake4
-rw-r--r--activerecord/lib/active_record/reflection.rb97
-rw-r--r--activerecord/lib/active_record/relation.rb45
-rw-r--r--activerecord/lib/active_record/relation/batches.rb8
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb80
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb27
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb77
-rw-r--r--activerecord/lib/active_record/schema.rb2
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb18
-rw-r--r--activerecord/lib/active_record/session_store.rb155
-rw-r--r--activerecord/lib/active_record/timestamp.rb69
-rw-r--r--activerecord/lib/active_record/validations/associated.rb7
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb22
-rw-r--r--activerecord/lib/active_record/version.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/active_schema_test.rb3
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb125
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb42
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb176
-rw-r--r--activerecord/test/cases/adapters/postgresql/active_schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb17
-rw-r--r--activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb98
-rw-r--r--activerecord/test/cases/aggregations_test.rb2
-rw-r--r--activerecord/test/cases/ar_schema_test.rb2
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb8
-rw-r--r--activerecord/test/cases/associations/callbacks_test.rb2
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb8
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb62
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb87
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb40
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb12
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations_test.rb67
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb280
-rw-r--r--activerecord/test/cases/autosave_association_test.rb10
-rw-r--r--activerecord/test/cases/base_test.rb855
-rw-r--r--activerecord/test/cases/calculations_test.rb2
-rw-r--r--activerecord/test/cases/column_definition_test.rb34
-rw-r--r--activerecord/test/cases/connection_management_test.rb25
-rw-r--r--activerecord/test/cases/connection_pool_test.rb50
-rw-r--r--activerecord/test/cases/defaults_test.rb2
-rw-r--r--activerecord/test/cases/finder_test.rb9
-rw-r--r--activerecord/test/cases/fixtures_test.rb6
-rw-r--r--activerecord/test/cases/i18n_test.rb2
-rw-r--r--activerecord/test/cases/invalid_date_test.rb2
-rw-r--r--activerecord/test/cases/json_serialization_test.rb7
-rw-r--r--activerecord/test/cases/locking_test.rb3
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb24
-rw-r--r--activerecord/test/cases/method_scoping_test.rb10
-rw-r--r--activerecord/test/cases/migration_test.rb12
-rw-r--r--activerecord/test/cases/named_scope_test.rb16
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb69
-rw-r--r--activerecord/test/cases/persistence_test.rb470
-rw-r--r--activerecord/test/cases/primary_keys_test.rb (renamed from activerecord/test/cases/pk_test.rb)4
-rw-r--r--activerecord/test/cases/query_cache_test.rb4
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb29
-rw-r--r--activerecord/test/cases/relations_test.rb24
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb4
-rw-r--r--activerecord/test/cases/serialization_test.rb134
-rw-r--r--activerecord/test/cases/session_store/session_test.rb68
-rw-r--r--activerecord/test/cases/session_store/sql_bypass.rb56
-rw-r--r--activerecord/test/cases/timestamp_test.rb30
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb42
-rw-r--r--activerecord/test/cases/transactions_test.rb23
-rw-r--r--activerecord/test/cases/validations/i18n_generate_message_validation_test.rb1
-rw-r--r--activerecord/test/cases/validations_test.rb5
-rw-r--r--activerecord/test/cases/xml_serialization_test.rb1
-rw-r--r--activerecord/test/connections/native_mysql2/connection.rb25
-rw-r--r--activerecord/test/fixtures/dashboards.yml3
-rw-r--r--activerecord/test/fixtures/minivans.yml5
-rw-r--r--activerecord/test/fixtures/speedometers.yml4
-rw-r--r--activerecord/test/fixtures/subscriptions.yml2
-rw-r--r--activerecord/test/models/author.rb2
-rw-r--r--activerecord/test/models/book.rb3
-rw-r--r--activerecord/test/models/company_in_module.rb2
-rw-r--r--activerecord/test/models/country.rb7
-rw-r--r--activerecord/test/models/dashboard.rb3
-rw-r--r--activerecord/test/models/developer.rb1
-rw-r--r--activerecord/test/models/electron.rb3
-rw-r--r--activerecord/test/models/liquid.rb5
-rw-r--r--activerecord/test/models/minivan.rb9
-rw-r--r--activerecord/test/models/molecule.rb4
-rw-r--r--activerecord/test/models/person.rb2
-rw-r--r--activerecord/test/models/reference.rb5
-rw-r--r--activerecord/test/models/speedometer.rb4
-rw-r--r--activerecord/test/models/treaty.rb7
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb24
-rw-r--r--activerecord/test/schema/schema.rb48
131 files changed, 4355 insertions, 2381 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index a1a82fdff5..20b2286fc0 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,4 +1,6 @@
-*Rails 3.0.0 [RC1] (unreleased)*
+*Rails 3.0.0 [release candidate] (July 26th, 2010)*
+
+* Changed update_attribute to not run callbacks and update the record directly in the database [Neeraj Singh]
* Add scoping and unscoped as the syntax to replace the old with_scope and with_exclusive_scope [José Valim]
diff --git a/activerecord/README b/activerecord/README
deleted file mode 100644
index d68eb28a64..0000000000
--- a/activerecord/README
+++ /dev/null
@@ -1,351 +0,0 @@
-= Active Record -- Object-relation mapping put on rails
-
-Active Record connects business objects and database tables to create a persistable
-domain model where logic and data are presented in one wrapping. It's an implementation
-of the object-relational mapping (ORM) pattern[http://www.martinfowler.com/eaaCatalog/activeRecord.html]
-by the same name as described by Martin Fowler:
-
- "An object that wraps a row in a database table or view, encapsulates
- the database access, and adds domain logic on that data."
-
-Active Record's main contribution to the pattern is to relieve the original of two stunting problems:
-lack of associations and inheritance. By adding a simple domain language-like set of macros to describe
-the former and integrating the Single Table Inheritance pattern for the latter, Active Record narrows the
-gap of functionality between the data mapper and active record approach.
-
-A short rundown of the major features:
-
-* Automated mapping between classes and tables, attributes and columns.
-
- class Product < ActiveRecord::Base; end
-
- ...is automatically mapped to the table named "products", such as:
-
- CREATE TABLE products (
- id int(11) NOT NULL auto_increment,
- name varchar(255),
- PRIMARY KEY (id)
- );
-
- ...which again gives Product#name and Product#name=(new_name)
-
- {Learn more}[link:classes/ActiveRecord/Base.html]
-
-
-* Associations between objects controlled by simple meta-programming macros.
-
- class Firm < ActiveRecord::Base
- has_many :clients
- has_one :account
- belongs_to :conglomorate
- end
-
- {Learn more}[link:classes/ActiveRecord/Associations/ClassMethods.html]
-
-
-* Aggregations of value objects controlled by simple meta-programming macros.
-
- class Account < ActiveRecord::Base
- composed_of :balance, :class_name => "Money",
- :mapping => %w(balance amount)
- composed_of :address,
- :mapping => [%w(address_street street), %w(address_city city)]
- end
-
- {Learn more}[link:classes/ActiveRecord/Aggregations/ClassMethods.html]
-
-
-* Validation rules that can differ for new or existing objects.
-
- class Account < ActiveRecord::Base
- validates_presence_of :subdomain, :name, :email_address, :password
- validates_uniqueness_of :subdomain
- validates_acceptance_of :terms_of_service, :on => :create
- validates_confirmation_of :password, :email_address, :on => :create
- end
-
- {Learn more}[link:classes/ActiveRecord/Validations.html]
-
-* Callbacks as methods or queues on the entire lifecycle (instantiation, saving, destroying, validating, etc).
-
- class Person < ActiveRecord::Base
- def before_destroy # is called just before Person#destroy
- CreditCard.find(credit_card_id).destroy
- end
- end
-
- class Account < ActiveRecord::Base
- after_find :eager_load, 'self.class.announce(#{id})'
- end
-
- {Learn more}[link:classes/ActiveRecord/Callbacks.html]
-
-
-* Observers for the entire lifecycle
-
- class CommentObserver < ActiveRecord::Observer
- def after_create(comment) # is called just after Comment#save
- Notifications.deliver_new_comment("david@loudthinking.com", comment)
- end
- end
-
- {Learn more}[link:classes/ActiveRecord/Observer.html]
-
-
-* Inheritance hierarchies
-
- class Company < ActiveRecord::Base; end
- class Firm < Company; end
- class Client < Company; end
- class PriorityClient < Client; end
-
- {Learn more}[link:classes/ActiveRecord/Base.html]
-
-
-* Transactions
-
- # Database transaction
- Account.transaction do
- david.withdrawal(100)
- mary.deposit(100)
- end
-
- {Learn more}[link:classes/ActiveRecord/Transactions/ClassMethods.html]
-
-
-* Reflections on columns, associations, and aggregations
-
- reflection = Firm.reflect_on_association(:clients)
- reflection.klass # => Client (class)
- Firm.columns # Returns an array of column descriptors for the firms table
-
- {Learn more}[link:classes/ActiveRecord/Reflection/ClassMethods.html]
-
-
-* Direct manipulation (instead of service invocation)
-
- So instead of (Hibernate[http://www.hibernate.org/] example):
-
- long pkId = 1234;
- DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) );
- // something interesting involving a cat...
- sess.save(cat);
- sess.flush(); // force the SQL INSERT
-
- Active Record lets you:
-
- pkId = 1234
- cat = Cat.find(pkId)
- # something even more interesting involving the same cat...
- cat.save
-
- {Learn more}[link:classes/ActiveRecord/Base.html]
-
-
-* Database abstraction through simple adapters (~100 lines) with a shared connector
-
- ActiveRecord::Base.establish_connection(:adapter => "sqlite", :database => "dbfile")
-
- ActiveRecord::Base.establish_connection(
- :adapter => "mysql",
- :host => "localhost",
- :username => "me",
- :password => "secret",
- :database => "activerecord"
- )
-
- {Learn more}[link:classes/ActiveRecord/Base.html#M000081] and read about the built-in support for
- MySQL[link:classes/ActiveRecord/ConnectionAdapters/MysqlAdapter.html], PostgreSQL[link:classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html], SQLite[link:classes/ActiveRecord/ConnectionAdapters/SQLiteAdapter.html], Oracle[link:classes/ActiveRecord/ConnectionAdapters/OracleAdapter.html], SQLServer[link:classes/ActiveRecord/ConnectionAdapters/SQLServerAdapter.html], and DB2[link:classes/ActiveRecord/ConnectionAdapters/DB2Adapter.html].
-
-
-* Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc]
-
- ActiveRecord::Base.logger = Logger.new(STDOUT)
- ActiveRecord::Base.logger = Log4r::Logger.new("Application Log")
-
-
-* Database agnostic schema management with Migrations
-
- class AddSystemSettings < ActiveRecord::Migration
- def self.up
- create_table :system_settings do |t|
- t.string :name
- t.string :label
- t.text :value
- t.string :type
- t.integer :position
- end
-
- SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
- end
-
- def self.down
- drop_table :system_settings
- end
- end
-
- {Learn more}[link:classes/ActiveRecord/Migration.html]
-
-== Simple example (1/2): Defining tables and classes (using MySQL)
-
-Data definitions are specified only in the database. Active Record queries the database for
-the column names (that then serves to determine which attributes are valid) on regular
-object instantiation through the new constructor and relies on the column names in the rows
-with the finders.
-
- # CREATE TABLE companies (
- # id int(11) unsigned NOT NULL auto_increment,
- # client_of int(11),
- # name varchar(255),
- # type varchar(100),
- # PRIMARY KEY (id)
- # )
-
-Active Record automatically links the "Company" object to the "companies" table
-
- class Company < ActiveRecord::Base
- has_many :people, :class_name => "Person"
- end
-
- class Firm < Company
- has_many :clients
-
- def people_with_all_clients
- clients.inject([]) { |people, client| people + client.people }
- end
- end
-
-The foreign_key is only necessary because we didn't use "firm_id" in the data definition
-
- class Client < Company
- belongs_to :firm, :foreign_key => "client_of"
- end
-
- # CREATE TABLE people (
- # id int(11) unsigned NOT NULL auto_increment,
- # name text,
- # company_id text,
- # PRIMARY KEY (id)
- # )
-
-Active Record will also automatically link the "Person" object to the "people" table
-
- class Person < ActiveRecord::Base
- belongs_to :company
- end
-
-== Simple example (2/2): Using the domain
-
-Picking a database connection for all the Active Records
-
- ActiveRecord::Base.establish_connection(
- :adapter => "mysql",
- :host => "localhost",
- :username => "me",
- :password => "secret",
- :database => "activerecord"
- )
-
-Create some fixtures
-
- firm = Firm.new("name" => "Next Angle")
- # SQL: INSERT INTO companies (name, type) VALUES("Next Angle", "Firm")
- firm.save
-
- client = Client.new("name" => "37signals", "client_of" => firm.id)
- # SQL: INSERT INTO companies (name, client_of, type) VALUES("37signals", 1, "Firm")
- client.save
-
-Lots of different finders
-
- # SQL: SELECT * FROM companies WHERE id = 1
- next_angle = Company.find(1)
-
- # SQL: SELECT * FROM companies WHERE id = 1 AND type = 'Firm'
- next_angle = Firm.find(1)
-
- # SQL: SELECT * FROM companies WHERE id = 1 AND name = 'Next Angle'
- next_angle = Company.find(:first, :conditions => "name = 'Next Angle'")
-
- next_angle = Firm.find_by_sql("SELECT * FROM companies WHERE id = 1").first
-
-The supertype, Company, will return subtype instances
-
- Firm === next_angle
-
-All the dynamic methods added by the has_many macro
-
- next_angle.clients.empty? # true
- next_angle.clients.size # total number of clients
- all_clients = next_angle.clients
-
-Constrained finds makes access security easier when ID comes from a web-app
-
- # SQL: SELECT * FROM companies WHERE client_of = 1 AND type = 'Client' AND id = 2
- thirty_seven_signals = next_angle.clients.find(2)
-
-Bi-directional associations thanks to the "belongs_to" macro
-
- thirty_seven_signals.firm.nil? # true
-
-
-== Philosophy
-
-Active Record attempts to provide a coherent wrapper as a solution for the inconvenience that is
-object-relational mapping. The prime directive for this mapping has been to minimize
-the amount of code needed to build a real-world domain model. This is made possible
-by relying on a number of conventions that make it easy for Active Record to infer
-complex relations and structures from a minimal amount of explicit direction.
-
-Convention over Configuration:
-* No XML-files!
-* Lots of reflection and run-time extension
-* Magic is not inherently a bad word
-
-Admit the Database:
-* Lets you drop down to SQL for odd cases and performance
-* Doesn't attempt to duplicate or replace data definitions
-
-
-== Download
-
-The latest version of Active Record can be found at
-
-* http://rubyforge.org/project/showfiles.php?group_id=182
-
-Documentation can be found at
-
-* http://ar.rubyonrails.com
-
-
-== Installation
-
-The prefered method of installing Active Record is through its GEM file. You'll need to have
-RubyGems[http://rubygems.rubyforge.org/wiki/wiki.pl] installed for that, though. If you have,
-then use:
-
- % [sudo] gem install activerecord-1.10.0.gem
-
-You can also install Active Record the old-fashioned way with the following command:
-
- % [sudo] ruby install.rb
-
-from its distribution directory.
-
-
-== License
-
-Active Record is released under the MIT license.
-
-
-== Support
-
-The Active Record homepage is http://www.rubyonrails.com. You can find the Active Record
-RubyForge page at http://rubyforge.org/projects/activerecord. And as Jim from Rake says:
-
- Feel free to submit commits or feature requests. If you send a patch,
- remember to update the corresponding unit tests. If fact, I prefer
- new feature to be submitted in the form of new unit tests.
-
-For other information, feel free to ask on the rubyonrails-talk
-(http://groups.google.com/group/rubyonrails-talk) mailing list.
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
new file mode 100644
index 0000000000..8dbd6c82b5
--- /dev/null
+++ b/activerecord/README.rdoc
@@ -0,0 +1,222 @@
+= Active Record -- Object-relational mapping put on rails
+
+Active Record connects classes to relational database tables to establish an
+almost zero-configuration persistence layer for applications. The library
+provides a base class that, when subclassed, sets up a mapping between the new
+class and an existing table in the database. In context of an application,
+these classes are commonly referred to as *models*. Models can also be
+connected to other models; this is done by defining *associations*.
+
+Active Record relies heavily on naming in that it uses class and association
+names to establish mappings between respective database tables and foreign key
+columns. Although these mappings can be defined explicitly, it's recommended
+to follow naming conventions, especially when getting started with the
+library.
+
+A short rundown of some of the major features:
+
+* Automated mapping between classes and tables, attributes and columns.
+
+ class Product < ActiveRecord::Base
+ end
+
+ The Product class is automatically mapped to the table named "products",
+ which might look like this:
+
+ CREATE TABLE products (
+ id int(11) NOT NULL auto_increment,
+ name varchar(255),
+ PRIMARY KEY (id)
+ );
+
+ This would also define the following accessors: `Product#name` and
+ `Product#name=(new_name)`
+
+ {Learn more}[link:classes/ActiveRecord/Base.html]
+
+
+* Associations between objects defined by simple class methods.
+
+ class Firm < ActiveRecord::Base
+ has_many :clients
+ has_one :account
+ belongs_to :conglomerate
+ end
+
+ {Learn more}[link:classes/ActiveRecord/Associations/ClassMethods.html]
+
+
+* Aggregations of value objects.
+
+ class Account < ActiveRecord::Base
+ composed_of :balance, :class_name => "Money",
+ :mapping => %w(balance amount)
+ composed_of :address,
+ :mapping => [%w(address_street street), %w(address_city city)]
+ end
+
+ {Learn more}[link:classes/ActiveRecord/Aggregations/ClassMethods.html]
+
+
+* Validation rules that can differ for new or existing objects.
+
+ class Account < ActiveRecord::Base
+ validates_presence_of :subdomain, :name, :email_address, :password
+ validates_uniqueness_of :subdomain
+ validates_acceptance_of :terms_of_service, :on => :create
+ validates_confirmation_of :password, :email_address, :on => :create
+ end
+
+ {Learn more}[link:classes/ActiveRecord/Validations.html]
+
+
+* Callbacks available for the entire lifecycle (instantiation, saving, destroying, validating, etc.)
+
+ class Person < ActiveRecord::Base
+ before_destroy :invalidate_payment_plan
+ # the `invalidate_payment_plan` method gets called just before Person#destroy
+ end
+
+ {Learn more}[link:classes/ActiveRecord/Callbacks.html]
+
+
+* Observers that react to changes in a model
+
+ class CommentObserver < ActiveRecord::Observer
+ def after_create(comment) # is called just after Comment#save
+ Notifications.deliver_new_comment("david@loudthinking.com", comment)
+ end
+ end
+
+ {Learn more}[link:classes/ActiveRecord/Observer.html]
+
+
+* Inheritance hierarchies
+
+ class Company < ActiveRecord::Base; end
+ class Firm < Company; end
+ class Client < Company; end
+ class PriorityClient < Client; end
+
+ {Learn more}[link:classes/ActiveRecord/Base.html]
+
+
+* Transactions
+
+ # Database transaction
+ Account.transaction do
+ david.withdrawal(100)
+ mary.deposit(100)
+ end
+
+ {Learn more}[link:classes/ActiveRecord/Transactions/ClassMethods.html]
+
+
+* Reflections on columns, associations, and aggregations
+
+ reflection = Firm.reflect_on_association(:clients)
+ reflection.klass # => Client (class)
+ Firm.columns # Returns an array of column descriptors for the firms table
+
+ {Learn more}[link:classes/ActiveRecord/Reflection/ClassMethods.html]
+
+
+* Database abstraction through simple adapters
+
+ # connect to SQLite3
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => "dbfile.sqlite3")
+
+ # connect to MySQL with authentication
+ ActiveRecord::Base.establish_connection(
+ :adapter => "mysql",
+ :host => "localhost",
+ :username => "me",
+ :password => "secret",
+ :database => "activerecord"
+ )
+
+ {Learn more}[link:classes/ActiveRecord/Base.html] and read about the built-in support for
+ MySQL[link:classes/ActiveRecord/ConnectionAdapters/MysqlAdapter.html],
+ PostgreSQL[link:classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html], and
+ SQLite3[link:classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html].
+
+
+* Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc]
+
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
+ ActiveRecord::Base.logger = Log4r::Logger.new("Application Log")
+
+
+* Database agnostic schema management with Migrations
+
+ class AddSystemSettings < ActiveRecord::Migration
+ def self.up
+ create_table :system_settings do |t|
+ t.string :name
+ t.string :label
+ t.text :value
+ t.string :type
+ t.integer :position
+ end
+
+ SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
+ end
+
+ def self.down
+ drop_table :system_settings
+ end
+ end
+
+ {Learn more}[link:classes/ActiveRecord/Migration.html]
+
+
+== Philosophy
+
+Active Record is an implementation of the object-relational mapping (ORM)
+pattern[http://www.martinfowler.com/eaaCatalog/activeRecord.html] by the same
+name described by Martin Fowler:
+
+ "An object that wraps a row in a database table or view,
+ encapsulates the database access, and adds domain logic on that data."
+
+Active Record attempts to provide a coherent wrapper as a solution for the inconvenience that is
+object-relational mapping. The prime directive for this mapping has been to minimize
+the amount of code needed to build a real-world domain model. This is made possible
+by relying on a number of conventions that make it easy for Active Record to infer
+complex relations and structures from a minimal amount of explicit direction.
+
+Convention over Configuration:
+* No XML-files!
+* Lots of reflection and run-time extension
+* Magic is not inherently a bad word
+
+Admit the Database:
+* Lets you drop down to SQL for odd cases and performance
+* Doesn't attempt to duplicate or replace data definitions
+
+
+== Download and installation
+
+The latest version of Active Record can be installed with Rubygems:
+
+ % [sudo] gem install activerecord
+
+Source code can be downloaded as part of the Rails project on GitHub
+
+* http://github.com/rails/rails/tree/master/activerecord/
+
+
+== License
+
+Active Record is released under the MIT license.
+
+
+== Support
+
+API documentation is at
+
+* http://api.rubyonrails.com
+
+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
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 392b717e0a..c1e90cc099 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -1,8 +1,8 @@
-gem 'rdoc', '= 2.2'
+gem 'rdoc', '>= 2.5.9'
require 'rdoc'
require 'rake'
require 'rake/testtask'
-require 'rake/rdoctask'
+require 'rdoc/task'
require 'rake/packagetask'
require 'rake/gempackagetask'
@@ -24,14 +24,14 @@ def run_without_aborting(*tasks)
abort "Errors running #{errors.join(', ')}" if errors.any?
end
-desc 'Run mysql, sqlite, and postgresql tests by default'
+desc 'Run mysql, mysql2, sqlite, and postgresql tests by default'
task :default => :test
-desc 'Run mysql, sqlite, and postgresql tests'
+desc 'Run mysql, mysql2, sqlite, and postgresql tests'
task :test do
tasks = defined?(JRUBY_VERSION) ?
%w(test_jdbcmysql test_jdbcsqlite3 test_jdbcpostgresql) :
- %w(test_mysql test_sqlite3 test_postgresql)
+ %w(test_mysql test_mysql2 test_sqlite3 test_postgresql)
run_without_aborting(*tasks)
end
@@ -39,15 +39,15 @@ namespace :test do
task :isolated do
tasks = defined?(JRUBY_VERSION) ?
%w(isolated_test_jdbcmysql isolated_test_jdbcsqlite3 isolated_test_jdbcpostgresql) :
- %w(isolated_test_mysql isolated_test_sqlite3 isolated_test_postgresql)
+ %w(isolated_test_mysql isolated_test_mysql2 isolated_test_sqlite3 isolated_test_postgresql)
run_without_aborting(*tasks)
end
end
-%w( mysql postgresql sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter|
+%w( mysql mysql2 postgresql sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter|
Rake::TestTask.new("test_#{adapter}") { |t|
connection_path = "test/connections/#{adapter =~ /jdbc/ ? 'jdbc' : 'native'}_#{adapter}"
- adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z]+/]
+ adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/]
t.libs << "test" << connection_path
t.test_files = (Dir.glob( "test/cases/**/*_test.rb" ).reject {
|x| x =~ /\/adapters\//
@@ -59,7 +59,7 @@ end
task "isolated_test_#{adapter}" do
connection_path = "test/connections/#{adapter =~ /jdbc/ ? 'jdbc' : 'native'}_#{adapter}"
- adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z]+/]
+ adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/]
puts [adapter, adapter_short, connection_path].inspect
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
(Dir["test/cases/**/*_test.rb"].reject {
@@ -166,13 +166,13 @@ task :rebuild_frontbase_databases => 'frontbase:rebuild_databases'
# Generate the RDoc documentation
-Rake::RDocTask.new { |rdoc|
+RDoc::Task.new { |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = "Active Record -- Object-relation mapping put on rails"
- rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
+ rdoc.options << '-f' << 'horo'
+ rdoc.options << '--main' << 'README.rdoc'
rdoc.options << '--charset' << 'utf-8'
- rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
- rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG')
+ rdoc.rdoc_files.include('README.rdoc', 'RUNNING_UNIT_TESTS', 'CHANGELOG')
rdoc.rdoc_files.include('lib/**/*.rb')
rdoc.rdoc_files.exclude('lib/active_record/vendor/*')
rdoc.rdoc_files.include('dev-utils/*.rb')
@@ -224,9 +224,3 @@ task :release => :package do
Rake::Gemcutter::Tasks.new(spec).define
Rake::Task['gem:push'].invoke
end
-
-desc "Publish the API documentation"
-task :pdoc => [:rdoc] do
- require 'rake/contrib/sshpublisher'
- Rake::SshDirPublisher.new("rails@api.rubyonrails.org", "public_html/ar", "doc").upload
-end
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 5aea992801..67d521d56b 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -14,15 +14,15 @@ Gem::Specification.new do |s|
s.homepage = 'http://www.rubyonrails.org'
s.rubyforge_project = 'activerecord'
- s.files = Dir['CHANGELOG', 'README', 'examples/**/*', 'lib/**/*']
+ s.files = Dir['CHANGELOG', 'README.rdoc', 'examples/**/*', 'lib/**/*']
s.require_path = 'lib'
s.has_rdoc = true
- s.extra_rdoc_files = %w( README )
- s.rdoc_options.concat ['--main', 'README']
+ s.extra_rdoc_files = %w( README.rdoc )
+ s.rdoc_options.concat ['--main', 'README.rdoc']
s.add_dependency('activesupport', version)
s.add_dependency('activemodel', version)
s.add_dependency('arel', '~> 0.4.0')
- s.add_dependency('tzinfo', '~> 0.3.16')
+ s.add_dependency('tzinfo', '~> 0.3.22')
end
diff --git a/activerecord/examples/performance.rb b/activerecord/examples/performance.rb
index f7d358337c..a985cfcb66 100644
--- a/activerecord/examples/performance.rb
+++ b/activerecord/examples/performance.rb
@@ -58,7 +58,7 @@ end
sqlfile = File.expand_path("../performance.sql", __FILE__)
if File.exists?(sqlfile)
- mysql_bin = %w[mysql mysql5].select { |bin| `which #{bin}`.length > 0 }
+ mysql_bin = %w[mysql mysql5].detect { |bin| `which #{bin}`.length > 0 }
`#{mysql_bin} -u #{conn[:username]} #{"-p#{conn[:password]}" unless conn[:password].blank?} #{conn[:database]} < #{sqlfile}`
else
puts 'Generating data...'
diff --git a/activerecord/install.rb b/activerecord/install.rb
deleted file mode 100644
index c87398b1f4..0000000000
--- a/activerecord/install.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require 'rbconfig'
-require 'find'
-require 'ftools'
-
-include Config
-
-# this was adapted from rdoc's install.rb by ways of Log4r
-
-$sitedir = CONFIG["sitelibdir"]
-unless $sitedir
- version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
- $libdir = File.join(CONFIG["libdir"], "ruby", version)
- $sitedir = $:.find {|x| x =~ /site_ruby/ }
- if !$sitedir
- $sitedir = File.join($libdir, "site_ruby")
- elsif $sitedir !~ Regexp.quote(version)
- $sitedir = File.join($sitedir, version)
- end
-end
-
-# the actual gruntwork
-Dir.chdir("lib")
-
-Find.find("active_record", "active_record.rb") { |f|
- if f[-3..-1] == ".rb"
- File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
- else
- File::makedirs(File.join($sitedir, *f.split(/\//)))
- end
-}
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index c45400d3d9..83a9ab46c5 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -9,11 +9,13 @@ module ActiveRecord
end unless self.new_record?
end
- # Active Record implements aggregation through a macro-like class method called +composed_of+ for representing attributes
- # as value objects. It expresses relationships like "Account [is] composed of Money [among other things]" or "Person [is]
- # composed of [an] address". Each call to the macro adds a description of how the value objects are created from the
- # attributes of the entity object (when the entity is initialized either as a new object or from finding an existing object)
- # and how it can be turned back into attributes (when the entity is saved to the database). Example:
+ # Active Record implements aggregation through a macro-like class method called +composed_of+
+ # for representing attributes as value objects. It expresses relationships like "Account [is]
+ # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call
+ # to the macro adds a description of how the value objects are created from the attributes of
+ # the entity object (when the entity is initialized either as a new object or from finding an
+ # existing object) and how it can be turned back into attributes (when the entity is saved to
+ # the database).
#
# class Customer < ActiveRecord::Base
# composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
@@ -68,9 +70,10 @@ module ActiveRecord
# end
# end
#
- # Now it's possible to access attributes from the database through the value objects instead. If you choose to name the
- # composition the same as the attribute's name, it will be the only way to access that attribute. That's the case with our
- # +balance+ attribute. You interact with the value objects just like you would any other attribute, though:
+ # Now it's possible to access attributes from the database through the value objects instead. If
+ # you choose to name the composition the same as the attribute's name, it will be the only way to
+ # access that attribute. That's the case with our +balance+ attribute. You interact with the value
+ # objects just like you would any other attribute, though:
#
# customer.balance = Money.new(20) # sets the Money value object and the attribute
# customer.balance # => Money value object
@@ -79,8 +82,8 @@ module ActiveRecord
# customer.balance == Money.new(20) # => true
# customer.balance < Money.new(5) # => false
#
- # Value objects can also be composed of multiple attributes, such as the case of Address. The order of the mappings will
- # determine the order of the parameters. Example:
+ # Value objects can also be composed of multiple attributes, such as the case of Address. The order
+ # of the mappings will determine the order of the parameters.
#
# customer.address_street = "Hyancintvej"
# customer.address_city = "Copenhagen"
@@ -91,38 +94,43 @@ module ActiveRecord
#
# == Writing value objects
#
- # Value objects are immutable and interchangeable objects that represent a given value, such as a Money object representing
- # $5. Two Money objects both representing $5 should be equal (through methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking
- # makes sense). This is unlike entity objects where equality is determined by identity. An entity class such as Customer can
- # easily have two different objects that both have an address on Hyancintvej. Entity identity is determined by object or
- # relational unique identifiers (such as primary keys). Normal ActiveRecord::Base classes are entity objects.
+ # Value objects are immutable and interchangeable objects that represent a given value, such as
+ # a Money object representing $5. Two Money objects both representing $5 should be equal (through
+ # methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking makes sense). This is
+ # unlike entity objects where equality is determined by identity. An entity class such as Customer can
+ # easily have two different objects that both have an address on Hyancintvej. Entity identity is
+ # determined by object or relational unique identifiers (such as primary keys). Normal
+ # ActiveRecord::Base classes are entity objects.
#
- # It's also important to treat the value objects as immutable. Don't allow the Money object to have its amount changed after
- # creation. Create a new Money object with the new value instead. This is exemplified by the Money#exchange_to method that
- # returns a new value object instead of changing its own values. Active Record won't persist value objects that have been
- # changed through means other than the writer method.
+ # It's also important to treat the value objects as immutable. Don't allow the Money object to have
+ # its amount changed after creation. Create a new Money object with the new value instead. This
+ # is exemplified by the Money#exchange_to method that returns a new value object instead of changing
+ # its own values. Active Record won't persist value objects that have been changed through means
+ # other than the writer method.
#
- # The immutable requirement is enforced by Active Record by freezing any object assigned as a value object. Attempting to
- # change it afterwards will result in a ActiveSupport::FrozenObjectError.
+ # The immutable requirement is enforced by Active Record by freezing any object assigned as a value
+ # object. Attempting to change it afterwards will result in a ActiveSupport::FrozenObjectError.
#
- # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not keeping value objects
- # immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
+ # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not
+ # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
#
# == Custom constructors and converters
#
- # By default value objects are initialized by calling the <tt>new</tt> constructor of the value class passing each of the
- # mapped attributes, in the order specified by the <tt>:mapping</tt> option, as arguments. If the value class doesn't support
- # this convention then +composed_of+ allows a custom constructor to be specified.
+ # By default value objects are initialized by calling the <tt>new</tt> constructor of the value
+ # class passing each of the mapped attributes, in the order specified by the <tt>:mapping</tt>
+ # option, as arguments. If the value class doesn't support this convention then +composed_of+ allows
+ # a custom constructor to be specified.
#
- # When a new value is assigned to the value object the default assumption is that the new value is an instance of the value
- # class. Specifying a custom converter allows the new value to be automatically converted to an instance of value class if
- # necessary.
+ # When a new value is assigned to the value object the default assumption is that the new value
+ # is an instance of the value class. Specifying a custom converter allows the new value to be automatically
+ # converted to an instance of value class if necessary.
#
- # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that should be aggregated using the
- # NetAddr::CIDR value class (http://netaddr.rubyforge.org). The constructor for the value class is called +create+ and it
- # expects a CIDR address string as a parameter. New values can be assigned to the value object using either another
- # NetAddr::CIDR object, a string or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to
- # meet these requirements:
+ # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that
+ # should be aggregated using the NetAddr::CIDR value class (http://netaddr.rubyforge.org). The constructor
+ # for the value class is called +create+ and it expects a CIDR address string as a parameter. New
+ # values can be assigned to the value object using either another NetAddr::CIDR object, a string
+ # or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet
+ # these requirements:
#
# class NetworkResource < ActiveRecord::Base
# composed_of :cidr,
@@ -149,9 +157,9 @@ module ActiveRecord
#
# == Finding records by a value object
#
- # Once a +composed_of+ relationship is specified for a model, records can be loaded from the database by specifying an instance
- # of the value object in the conditions hash. The following example finds all customers with +balance_amount+ equal to 20 and
- # +balance_currency+ equal to "USD":
+ # Once a +composed_of+ relationship is specified for a model, records can be loaded from the database
+ # by specifying an instance of the value object in the conditions hash. The following example
+ # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD":
#
# Customer.find(:all, :conditions => {:balance => Money.new(20, "USD")})
#
@@ -160,23 +168,28 @@ module ActiveRecord
# <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.
#
# Options are:
- # * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name can't be inferred
- # from the part id. So <tt>composed_of :address</tt> will by default be linked to the Address class, but
- # if the real class name is CompanyAddress, you'll have to specify it with this option.
- # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value object. Each mapping
- # is represented as an array where the first item is the name of the entity attribute and the second item is the
- # name the attribute in the value object. The order in which mappings are defined determine the order in which
- # attributes are sent to the value class constructor.
+ # * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name
+ # can't be inferred from the part id. So <tt>composed_of :address</tt> will by default be linked
+ # to the Address class, but if the real class name is CompanyAddress, you'll have to specify it
+ # with this option.
+ # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value
+ # object. Each mapping is represented as an array where the first item is the name of the
+ # entity attribute and the second item is the name the attribute in the value object. The
+ # order in which mappings are defined determine the order in which attributes are sent to the
+ # value class constructor.
# * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
- # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all mapped attributes.
+ # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
+ # mapped attributes.
# This defaults to +false+.
- # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that is called to
- # initialize the value object. The constructor is passed all of the mapped attributes, in the order that they
- # are defined in the <tt>:mapping option</tt>, as arguments and uses them to instantiate a <tt>:class_name</tt> object.
+ # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that
+ # is called to initialize the value object. The constructor is passed all of the mapped attributes,
+ # in the order that they are defined in the <tt>:mapping option</tt>, as arguments and uses them
+ # to instantiate a <tt>:class_name</tt> object.
# The default is <tt>:new</tt>.
- # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt> or a Proc that is
- # called when a new value is assigned to the value object. The converter is passed the single value that is used
- # in the assignment and is only called if the new value is not an instance of <tt>:class_name</tt>.
+ # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt>
+ # or a Proc that is called when a new value is assigned to the value object. The converter is
+ # passed the single value that is used in the assignment and is only called if the new value is
+ # not an instance of <tt>:class_name</tt>.
#
# Option examples:
# composed_of :temperature, :mapping => %w(reading celsius)
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index cbec5789fd..0f0fdc2e21 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -9,8 +9,8 @@ module ActiveRecord
# Implements the details of eager loading of Active Record associations.
# Application developers should not use this module directly.
#
- # ActiveRecord::Base is extended with this module. The source code in
- # ActiveRecord::Base references methods defined in this module.
+ # <tt>ActiveRecord::Base</tt> is extended with this module. The source code in
+ # <tt>ActiveRecord::Base</tt> references methods defined in this module.
#
# Note that 'eager loading' and 'preloading' are actually the same thing.
# However, there are two different eager loading strategies.
@@ -55,7 +55,7 @@ module ActiveRecord
# == Parameters
# +records+ is an array of ActiveRecord::Base. This array needs not be flat,
# i.e. +records+ itself may also contain arrays of records. In any case,
- # +preload_associations+ will preload the associations all records by
+ # +preload_associations+ will preload the all associations records by
# flattening +records+.
#
# +associations+ specifies one or more associations that you want to
@@ -110,15 +110,15 @@ module ActiveRecord
def preload_one_association(records, association, preload_options={})
class_to_reflection = {}
# Not all records have the same class, so group then preload
- # group on the reflection itself so that if various subclass share the same association then we do not split them
- # unnecessarily
- records.group_by {|record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, records|
+ # group on the reflection itself so that if various subclass share the same association then
+ # we do not split them unnecessarily
+ records.group_by { |record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, _records|
raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection
# 'reflection.macro' can return 'belongs_to', 'has_many', etc. Thus,
# the following could call 'preload_belongs_to_association',
# 'preload_has_many_association', etc.
- send("preload_#{reflection.macro}_association", records, reflection, preload_options)
+ send("preload_#{reflection.macro}_association", _records, reflection, preload_options)
end
end
@@ -149,7 +149,8 @@ module ActiveRecord
seen_keys = {}
associated_records.each do |associated_record|
#this is a has_one or belongs_to: there should only be one record.
- #Unfortunately we can't (in portable way) ask the database for 'all records where foo_id in (x,y,z), but please
+ #Unfortunately we can't (in portable way) ask the database for
+ #'all records where foo_id in (x,y,z), but please
# only one row per distinct foo_id' so this where we enforce that
next if seen_keys[associated_record[key].to_s]
seen_keys[associated_record[key].to_s] = true
@@ -304,7 +305,8 @@ module ActiveRecord
polymorph_type = options[:foreign_type]
klasses_and_ids = {}
- # Construct a mapping from klass to a list of ids to load and a mapping of those ids back to their parent_records
+ # Construct a mapping from klass to a list of ids to load and a mapping of those ids back
+ # to their parent_records
records.each do |record|
if klass = record.send(polymorph_type)
klass_id = record.send(primary_key_name)
@@ -378,7 +380,7 @@ module ActiveRecord
:order => preload_options[:order] || options[:order]
}
- reflection.klass.unscoped.apply_finder_options(find_options).to_a
+ reflection.klass.scoped.apply_finder_options(find_options).to_a
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 65daa8ffbe..73c0900c8b 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -3,6 +3,7 @@ require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/conversions'
+require 'active_support/core_ext/module/remove_method'
module ActiveRecord
class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
@@ -113,7 +114,7 @@ module ActiveRecord
autoload :HasOneAssociation, 'active_record/associations/has_one_association'
autoload :HasOneThroughAssociation, 'active_record/associations/has_one_through_association'
- # Clears out the association cache
+ # Clears out the association cache.
def clear_association_cache #:nodoc:
self.class.reflect_on_all_associations.to_a.each do |assoc|
instance_variable_set "@#{assoc.name}", nil
@@ -121,7 +122,7 @@ module ActiveRecord
end
private
- # Gets the specified association instance if it responds to :loaded?, nil otherwise.
+ # Returns the specified association instance if it responds to :loaded?, nil otherwise.
def association_instance_get(name)
ivar = "@#{name}"
if instance_variable_defined?(ivar)
@@ -135,10 +136,12 @@ module ActiveRecord
instance_variable_set("@#{name}", association)
end
- # Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like
- # "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are
- # specialized according to the collection or association symbol and the options hash. It works much the same way as Ruby's own <tt>attr*</tt>
- # methods. Example:
+ # Associations are a set of macro-like class methods for tying objects together through
+ # foreign keys. They express relationships like "Project has one Project Manager"
+ # or "Project belongs to a Portfolio". Each macro adds a number of methods to the
+ # class which are specialized according to the collection or association symbol and the
+ # options hash. It works much the same way as Ruby's own <tt>attr*</tt>
+ # methods.
#
# class Project < ActiveRecord::Base
# belongs_to :portfolio
@@ -147,7 +150,8 @@ module ActiveRecord
# has_and_belongs_to_many :categories
# end
#
- # The project class now has the following methods (and more) to ease the traversal and manipulation of its relationships:
+ # The project class now has the following methods (and more) to ease the traversal and
+ # manipulation of its relationships:
# * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
# * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
# * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
@@ -158,8 +162,9 @@ module ActiveRecord
#
# === A word of warning
#
- # Don't create associations that have the same name as instance methods of ActiveRecord::Base. Since the association
- # adds a method with that name to its model, it will override the inherited method and break things.
+ # Don't create associations that have the same name as instance methods of
+ # <tt>ActiveRecord::Base</tt>. Since the association adds a method with that name to
+ # its model, it will override the inherited method and break things.
# For instance, +attributes+ and +connection+ would be bad choices for association names.
#
# == Auto-generated methods
@@ -269,8 +274,8 @@ module ActiveRecord
#
# == Is it a +belongs_to+ or +has_one+ association?
#
- # Both express a 1-1 relationship. The difference is mostly where to place the foreign key, which goes on the table for the class
- # declaring the +belongs_to+ relationship. Example:
+ # Both express a 1-1 relationship. The difference is mostly where to place the foreign
+ # key, which goes on the table for the class declaring the +belongs_to+ relationship.
#
# class User < ActiveRecord::Base
# # I reference an account.
@@ -299,36 +304,44 @@ module ActiveRecord
#
# == Unsaved objects and associations
#
- # You can manipulate objects and associations before they are saved to the database, but there is some special behavior you should be
- # aware of, mostly involving the saving of associated objects.
+ # You can manipulate objects and associations before they are saved to the database, but
+ # there is some special behavior you should be aware of, mostly involving the saving of
+ # associated objects.
#
- # Unless you set the :autosave option on a <tt>has_one</tt>, <tt>belongs_to</tt>,
+ # You can set the :autosave option on a <tt>has_one</tt>, <tt>belongs_to</tt>,
# <tt>has_many</tt>, or <tt>has_and_belongs_to_many</tt> association. Setting it
# to +true+ will _always_ save the members, whereas setting it to +false+ will
# _never_ save the members.
#
# === One-to-one associations
#
- # * Assigning an object to a +has_one+ association automatically saves that object and the object being replaced (if there is one), in
- # order to update their primary keys - except if the parent object is unsaved (<tt>new_record? == true</tt>).
- # * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns +false+ and the assignment
- # is cancelled.
- # * If you wish to assign an object to a +has_one+ association without saving it, use the <tt>association.build</tt> method (documented below).
- # * Assigning an object to a +belongs_to+ association does not save the object, since the foreign key field belongs on the parent. It
- # does not save the parent either.
+ # * Assigning an object to a +has_one+ association automatically saves that object and
+ # the object being replaced (if there is one), in order to update their primary
+ # keys - except if the parent object is unsaved (<tt>new_record? == true</tt>).
+ # * If either of these saves fail (due to one of the objects being invalid) the assignment
+ # statement returns +false+ and the assignment is cancelled.
+ # * If you wish to assign an object to a +has_one+ association without saving it,
+ # use the <tt>association.build</tt> method (documented below).
+ # * Assigning an object to a +belongs_to+ association does not save the object, since
+ # the foreign key field belongs on the parent. It does not save the parent either.
#
# === Collections
#
- # * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically saves that object, except if the parent object
- # (the owner of the collection) is not yet stored in the database.
- # * If saving any of the objects being added to a collection (via <tt>push</tt> or similar) fails, then <tt>push</tt> returns +false+.
- # * You can add an object to a collection without automatically saving it by using the <tt>collection.build</tt> method (documented below).
- # * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically saved when the parent is saved.
+ # * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically
+ # saves that object, except if the parent object (the owner of the collection) is not yet
+ # stored in the database.
+ # * If saving any of the objects being added to a collection (via <tt>push</tt> or similar)
+ # fails, then <tt>push</tt> returns +false+.
+ # * You can add an object to a collection without automatically saving it by using the
+ # <tt>collection.build</tt> method (documented below).
+ # * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically
+ # saved when the parent is saved.
#
# === Association callbacks
#
- # Similar to the normal callbacks that hook into the lifecycle of an Active Record object, you can also define callbacks that get
- # triggered when you add an object to or remove an object from an association collection. Example:
+ # Similar to the normal callbacks that hook into the lifecycle of an Active Record object,
+ # you can also define callbacks that get triggered when you add an object to or remove an
+ # object from an association collection.
#
# class Project
# has_and_belongs_to_many :developers, :after_add => :evaluate_velocity
@@ -341,19 +354,21 @@ module ActiveRecord
# It's possible to stack callbacks by passing them as an array. Example:
#
# class Project
- # has_and_belongs_to_many :developers, :after_add => [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
+ # has_and_belongs_to_many :developers,
+ # :after_add => [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
# end
#
# Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+.
#
- # Should any of the +before_add+ callbacks throw an exception, the object does not get added to the collection. Same with
- # the +before_remove+ callbacks; if an exception is thrown the object doesn't get removed.
+ # Should any of the +before_add+ callbacks throw an exception, the object does not get
+ # added to the collection. Same with the +before_remove+ callbacks; if an exception is
+ # thrown the object doesn't get removed.
#
# === Association extensions
#
- # The proxy objects that control the access to associations can be extended through anonymous modules. This is especially
- # beneficial for adding new finders, creators, and other factory-type methods that are only used as part of this association.
- # Example:
+ # The proxy objects that control the access to associations can be extended through anonymous
+ # modules. This is especially beneficial for adding new finders, creators, and other
+ # factory-type methods that are only used as part of this association.
#
# class Account < ActiveRecord::Base
# has_many :people do
@@ -368,7 +383,8 @@ module ActiveRecord
# person.first_name # => "David"
# person.last_name # => "Heinemeier Hansson"
#
- # If you need to share the same extensions between many associations, you can use a named extension module. Example:
+ # If you need to share the same extensions between many associations, you can use a named
+ # extension module.
#
# module FindOrCreateByNameExtension
# def find_or_create_by_name(name)
@@ -385,9 +401,10 @@ module ActiveRecord
# has_many :people, :extend => FindOrCreateByNameExtension
# end
#
- # If you need to use multiple named extension modules, you can specify an array of modules with the <tt>:extend</tt> option.
- # In the case of name conflicts between methods in the modules, methods in modules later in the array supercede
- # those earlier in the array. Example:
+ # If you need to use multiple named extension modules, you can specify an array of modules
+ # with the <tt>:extend</tt> option.
+ # In the case of name conflicts between methods in the modules, methods in modules later
+ # in the array supercede those earlier in the array.
#
# class Account < ActiveRecord::Base
# has_many :people, :extend => [FindOrCreateByNameExtension, FindRecentExtension]
@@ -398,12 +415,14 @@ module ActiveRecord
#
# * +proxy_owner+ - Returns the object the association is part of.
# * +proxy_reflection+ - Returns the reflection object that describes the association.
- # * +proxy_target+ - Returns the associated object for +belongs_to+ and +has_one+, or the collection of associated objects for +has_many+ and +has_and_belongs_to_many+.
+ # * +proxy_target+ - Returns the associated object for +belongs_to+ and +has_one+, or
+ # the collection of associated objects for +has_many+ and +has_and_belongs_to_many+.
#
# === Association Join Models
#
- # Has Many associations can be configured with the <tt>:through</tt> option to use an explicit join model to retrieve the data. This
- # operates similarly to a +has_and_belongs_to_many+ association. The advantage is that you're able to add validations,
+ # Has Many associations can be configured with the <tt>:through</tt> option to use an
+ # explicit join model to retrieve the data. This operates similarly to a
+ # +has_and_belongs_to_many+ association. The advantage is that you're able to add validations,
# callbacks, and extra attributes on the join model. Consider the following schema:
#
# class Author < ActiveRecord::Base
@@ -417,7 +436,7 @@ module ActiveRecord
# end
#
# @author = Author.find :first
- # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to.
+ # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to
# @author.books # selects all books by using the Authorship join model
#
# You can also go through a +has_many+ association on the join model:
@@ -438,7 +457,7 @@ module ActiveRecord
#
# @firm = Firm.find :first
# @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
- # @firm.invoices # selects all invoices by going through the Client join model.
+ # @firm.invoices # selects all invoices by going through the Client join model
#
# Similarly you can go through a +has_one+ association on the join model:
#
@@ -460,16 +479,18 @@ module ActiveRecord
# @group.users.collect { |u| u.avatar }.flatten # select all avatars for all users in the group
# @group.avatars # selects all avatars by going through the User join model.
#
- # An important caveat with going through +has_one+ or +has_many+ associations on the join model is that these associations are
- # *read-only*. For example, the following would not work following the previous example:
+ # An important caveat with going through +has_one+ or +has_many+ associations on the
+ # join model is that these associations are *read-only*. For example, the following
+ # would not work following the previous example:
#
- # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around.
+ # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around
# @group.avatars.delete(@group.avatars.last) # so would this
#
# === Polymorphic Associations
#
- # Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they
- # specify an interface that a +has_many+ association must adhere to.
+ # Polymorphic associations on models are not restricted on what types of models they
+ # can be associated with. Rather, they specify an interface that a +has_many+ association
+ # must adhere to.
#
# class Asset < ActiveRecord::Base
# belongs_to :attachable, :polymorphic => true
@@ -481,13 +502,16 @@ module ActiveRecord
#
# @asset.attachable = @post
#
- # This works by using a type column in addition to a foreign key to specify the associated record. In the Asset example, you'd need
- # an +attachable_id+ integer column and an +attachable_type+ string column.
+ # This works by using a type column in addition to a foreign key to specify the associated
+ # record. In the Asset example, you'd need an +attachable_id+ integer column and an
+ # +attachable_type+ string column.
#
- # Using polymorphic associations in combination with single table inheritance (STI) is a little tricky. In order
- # for the associations to work as expected, ensure that you store the base model for the STI models in the
- # type column of the polymorphic association. To continue with the asset example above, suppose there are guest posts
- # and member posts that use the posts table for STI. In this case, there must be a +type+ column in the posts table.
+ # Using polymorphic associations in combination with single table inheritance (STI) is
+ # a little tricky. In order for the associations to work as expected, ensure that you
+ # store the base model for the STI models in the type column of the polymorphic
+ # association. To continue with the asset example above, suppose there are guest posts
+ # and member posts that use the posts table for STI. In this case, there must be a +type+
+ # column in the posts table.
#
# class Asset < ActiveRecord::Base
# belongs_to :attachable, :polymorphic => true
@@ -510,9 +534,10 @@ module ActiveRecord
#
# == Caching
#
- # All of the methods are built on a simple caching principle that will keep the result of the last query around unless specifically
- # instructed not to. The cache is even shared across methods to make it even cheaper to use the macro-added methods without
- # worrying too much about performance at the first go. Example:
+ # All of the methods are built on a simple caching principle that will keep the result
+ # of the last query around unless specifically instructed not to. The cache is even
+ # shared across methods to make it even cheaper to use the macro-added methods without
+ # worrying too much about performance at the first go.
#
# project.milestones # fetches milestones from the database
# project.milestones.size # uses the milestone cache
@@ -522,9 +547,10 @@ module ActiveRecord
#
# == Eager loading of associations
#
- # Eager loading is a way to find objects of a certain class and a number of named associations. This is
- # one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100 posts that each need to display their author
- # triggers 101 database queries. Through the use of eager loading, the 101 queries can be reduced to 2. Example:
+ # Eager loading is a way to find objects of a certain class and a number of named associations.
+ # This is one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100
+ # posts that each need to display their author triggers 101 database queries. Through the
+ # use of eager loading, the 101 queries can be reduced to 2.
#
# class Post < ActiveRecord::Base
# belongs_to :author
@@ -539,44 +565,55 @@ module ActiveRecord
# puts "Last comment on: " + post.comments.first.created_on
# end
#
- # To iterate over these one hundred posts, we'll generate 201 database queries. Let's first just optimize it for retrieving the author:
+ # To iterate over these one hundred posts, we'll generate 201 database queries. Let's
+ # first just optimize it for retrieving the author:
#
# for post in Post.find(:all, :include => :author)
#
- # This references the name of the +belongs_to+ association that also used the <tt>:author</tt> symbol. After loading the posts, find
- # will collect the +author_id+ from each one and load all the referenced authors with one query. Doing so will cut down the number of queries from 201 to 102.
+ # This references the name of the +belongs_to+ association that also used the <tt>:author</tt>
+ # symbol. After loading the posts, find will collect the +author_id+ from each one and load
+ # all the referenced authors with one query. Doing so will cut down the number of queries
+ # from 201 to 102.
#
# We can improve upon the situation further by referencing both associations in the finder with:
#
# for post in Post.find(:all, :include => [ :author, :comments ])
#
- # This will load all comments with a single query. This reduces the total number of queries to 3. More generally the number of queries
- # will be 1 plus the number of associations named (except if some of the associations are polymorphic +belongs_to+ - see below).
+ # This will load all comments with a single query. This reduces the total number of queries
+ # to 3. More generally the number of queries will be 1 plus the number of associations
+ # named (except if some of the associations are polymorphic +belongs_to+ - see below).
#
# To include a deep hierarchy of associations, use a hash:
#
# for post in Post.find(:all, :include => [ :author, { :comments => { :author => :gravatar } } ])
#
- # That'll grab not only all the comments but all their authors and gravatar pictures. You can mix and match
- # symbols, arrays and hashes in any combination to describe the associations you want to load.
+ # That'll grab not only all the comments but all their authors and gravatar pictures.
+ # You can mix and match symbols, arrays and hashes in any combination to describe the
+ # associations you want to load.
#
- # All of this power shouldn't fool you into thinking that you can pull out huge amounts of data with no performance penalty just because you've reduced
- # the number of queries. The database still needs to send all the data to Active Record and it still needs to be processed. So it's no
- # catch-all for performance problems, but it's a great way to cut down on the number of queries in a situation as the one described above.
+ # All of this power shouldn't fool you into thinking that you can pull out huge amounts
+ # of data with no performance penalty just because you've reduced the number of queries.
+ # The database still needs to send all the data to Active Record and it still needs to
+ # be processed. So it's no catch-all for performance problems, but it's a great way to
+ # cut down on the number of queries in a situation as the one described above.
#
- # Since only one table is loaded at a time, conditions or orders cannot reference tables other than the main one. If this is the case
- # Active Record falls back to the previously used LEFT OUTER JOIN based strategy. For example
+ # Since only one table is loaded at a time, conditions or orders cannot reference tables
+ # other than the main one. If this is the case Active Record falls back to the previously
+ # used LEFT OUTER JOIN based strategy. For example
#
# Post.find(:all, :include => [ :author, :comments ], :conditions => ['comments.approved = ?', true])
#
- # This will result in a single SQL query with joins along the lines of: <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and
- # <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions like this can have unintended consequences.
- # In the above example posts with no approved comments are not returned at all, because the conditions apply to the SQL statement as a whole
- # and not just to the association. You must disambiguate column references for this fallback to happen, for example
+ # This will result in a single SQL query with joins along the lines of:
+ # <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and
+ # <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions
+ # like this can have unintended consequences.
+ # In the above example posts with no approved comments are not returned at all, because
+ # the conditions apply to the SQL statement as a whole and not just to the association.
+ # You must disambiguate column references for this fallback to happen, for example
# <tt>:order => "author.name DESC"</tt> will work but <tt>:order => "name DESC"</tt> will not.
#
- # If you do want eager load only some members of an association it is usually more natural to <tt>:include</tt> an association
- # which has conditions defined on it:
+ # If you do want eager load only some members of an association it is usually more natural
+ # to <tt>:include</tt> an association which has conditions defined on it:
#
# class Post < ActiveRecord::Base
# has_many :approved_comments, :class_name => 'Comment', :conditions => ['approved = ?', true]
@@ -584,9 +621,11 @@ module ActiveRecord
#
# Post.find(:all, :include => :approved_comments)
#
- # This will load posts and eager load the +approved_comments+ association, which contains only those comments that have been approved.
+ # This will load posts and eager load the +approved_comments+ association, which contains
+ # only those comments that have been approved.
#
- # If you eager load an association with a specified <tt>:limit</tt> option, it will be ignored, returning all the associated objects:
+ # If you eager load an association with a specified <tt>:limit</tt> option, it will be ignored,
+ # returning all the associated objects:
#
# class Picture < ActiveRecord::Base
# has_many :most_recent_comments, :class_name => 'Comment', :order => 'id DESC', :limit => 10
@@ -594,8 +633,8 @@ module ActiveRecord
#
# Picture.find(:first, :include => :most_recent_comments).most_recent_comments # => returns all associated comments.
#
- # When eager loaded, conditions are interpolated in the context of the model class, not the model instance. Conditions are lazily interpolated
- # before the actual model exists.
+ # When eager loaded, conditions are interpolated in the context of the model class, not
+ # the model instance. Conditions are lazily interpolated before the actual model exists.
#
# Eager loading is supported with polymorphic associations.
#
@@ -607,17 +646,21 @@ module ActiveRecord
#
# Address.find(:all, :include => :addressable)
#
- # This will execute one query to load the addresses and load the addressables with one query per addressable type.
- # For example if all the addressables are either of class Person or Company then a total of 3 queries will be executed. The list of
- # addressable types to load is determined on the back of the addresses loaded. This is not supported if Active Record has to fallback
- # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent
- # model's type is a column value so its corresponding table name cannot be put in the +FROM+/+JOIN+ clauses of that query.
+ # This will execute one query to load the addresses and load the addressables with one
+ # query per addressable type.
+ # For example if all the addressables are either of class Person or Company then a total
+ # of 3 queries will be executed. The list of addressable types to load is determined on
+ # the back of the addresses loaded. This is not supported if Active Record has to fallback
+ # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError.
+ # The reason is that the parent model's type is a column value so its corresponding table
+ # name cannot be put in the +FROM+/+JOIN+ clauses of that query.
#
# == Table Aliasing
#
- # Active Record uses table aliasing in the case that a table is referenced multiple times in a join. If a table is referenced only once,
- # the standard table name is used. The second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>. Indexes are appended
- # for any more successive uses of the table name.
+ # Active Record uses table aliasing in the case that a table is referenced multiple times
+ # in a join. If a table is referenced only once, the standard table name is used. The
+ # second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>.
+ # Indexes are appended for any more successive uses of the table name.
#
# Post.find :all, :joins => :comments
# # => SELECT ... FROM posts INNER JOIN comments ON ...
@@ -650,7 +693,8 @@ module ActiveRecord
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
# INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2
#
- # If you wish to specify your own custom joins using a <tt>:joins</tt> option, those table names will take precedence over the eager associations:
+ # If you wish to specify your own custom joins using a <tt>:joins</tt> option, those table
+ # names will take precedence over the eager associations:
#
# Post.find :all, :joins => :comments, :joins => "inner join comments ..."
# # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ...
@@ -659,7 +703,8 @@ module ActiveRecord
# INNER JOIN comments special_comments_posts ...
# INNER JOIN comments ...
#
- # Table aliases are automatically truncated according to the maximum length of table identifiers according to the specific database.
+ # Table aliases are automatically truncated according to the maximum length of table identifiers
+ # according to the specific database.
#
# == Modules
#
@@ -675,9 +720,10 @@ module ActiveRecord
# end
# end
#
- # When <tt>Firm#clients</tt> is called, it will in turn call <tt>MyApplication::Business::Client.find_all_by_firm_id(firm.id)</tt>.
- # If you want to associate with a class in another module scope, this can be done by specifying the complete class name.
- # Example:
+ # When <tt>Firm#clients</tt> is called, it will in turn call
+ # <tt>MyApplication::Business::Client.find_all_by_firm_id(firm.id)</tt>.
+ # If you want to associate with a class in another module scope, this can be done by
+ # specifying the complete class name.
#
# module MyApplication
# module Business
@@ -693,8 +739,8 @@ module ActiveRecord
#
# == Bi-directional associations
#
- # When you specify an association there is usually an association on the associated model that specifies the same
- # relationship in reverse. For example, with the following models:
+ # When you specify an association there is usually an association on the associated model
+ # that specifies the same relationship in reverse. For example, with the following models:
#
# class Dungeon < ActiveRecord::Base
# has_many :traps
@@ -709,9 +755,11 @@ module ActiveRecord
# belongs_to :dungeon
# end
#
- # The +traps+ association on +Dungeon+ and the the +dungeon+ association on +Trap+ are the inverse of each other and the
- # inverse of the +dungeon+ association on +EvilWizard+ is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default,
- # Active Record doesn't know anything about these inverse relationships and so no object loading optimisation is possible. For example:
+ # The +traps+ association on +Dungeon+ and the the +dungeon+ association on +Trap+ are
+ # the inverse of each other and the inverse of the +dungeon+ association on +EvilWizard+
+ # is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default,
+ # Active Record doesn't know anything about these inverse relationships and so no object
+ # loading optimisation is possible. For example:
#
# d = Dungeon.first
# t = d.traps.first
@@ -719,9 +767,11 @@ module ActiveRecord
# d.level = 10
# d.level == t.dungeon.level # => false
#
- # The +Dungeon+ instances +d+ and <tt>t.dungeon</tt> in the above example refer to the same object data from the database, but are
- # actually different in-memory copies of that data. Specifying the <tt>:inverse_of</tt> option on associations lets you tell
- # Active Record about inverse relationships and it will optimise object loading. For example, if we changed our model definitions to:
+ # The +Dungeon+ instances +d+ and <tt>t.dungeon</tt> in the above example refer to
+ # the same object data from the database, but are actually different in-memory copies
+ # of that data. Specifying the <tt>:inverse_of</tt> option on associations lets you tell
+ # Active Record about inverse relationships and it will optimise object loading. For
+ # example, if we changed our model definitions to:
#
# class Dungeon < ActiveRecord::Base
# has_many :traps, :inverse_of => :dungeon
@@ -736,8 +786,8 @@ module ActiveRecord
# belongs_to :dungeon, :inverse_of => :evil_wizard
# end
#
- # Then, from our code snippet above, +d+ and <tt>t.dungeon</tt> are actually the same in-memory instance and our final <tt>d.level == t.dungeon.level</tt>
- # will return +true+.
+ # Then, from our code snippet above, +d+ and <tt>t.dungeon</tt> are actually the same
+ # in-memory instance and our final <tt>d.level == t.dungeon.level</tt> will return +true+.
#
# There are limitations to <tt>:inverse_of</tt> support:
#
@@ -747,13 +797,13 @@ module ActiveRecord
#
# == Type safety with <tt>ActiveRecord::AssociationTypeMismatch</tt>
#
- # If you attempt to assign an object to an association that doesn't match the inferred or specified <tt>:class_name</tt>, you'll
- # get an <tt>ActiveRecord::AssociationTypeMismatch</tt>.
+ # If you attempt to assign an object to an association that doesn't match the inferred
+ # or specified <tt>:class_name</tt>, you'll get an <tt>ActiveRecord::AssociationTypeMismatch</tt>.
#
# == Options
#
- # All of the association macros can be specialized through options. This makes cases more complex than the simple and guessable ones
- # possible.
+ # All of the association macros can be specialized through options. This makes cases
+ # more complex than the simple and guessable ones possible.
module ClassMethods
# Specifies a one-to-many association. The following methods for retrieval and query of
# collections of associated objects will be added:
@@ -827,20 +877,22 @@ module ActiveRecord
# === Supported options
# [:class_name]
# Specify the class name of the association. Use it only if that name can't be inferred
- # from the association name. So <tt>has_many :products</tt> will by default be linked to the Product class, but
- # if the real class name is SpecialProduct, you'll have to specify it with this option.
+ # from the association name. So <tt>has_many :products</tt> will by default be linked
+ # to the Product class, but if the real class name is SpecialProduct, you'll have to
+ # specify it with this option.
# [:conditions]
# Specify the conditions that the associated objects must meet in order to be included as a +WHERE+
- # SQL fragment, such as <tt>price > 5 AND name LIKE 'B%'</tt>. Record creations from the association are scoped if a hash
- # is used. <tt>has_many :posts, :conditions => {:published => true}</tt> will create published posts with <tt>@blog.posts.create</tt>
- # or <tt>@blog.posts.build</tt>.
+ # SQL fragment, such as <tt>price > 5 AND name LIKE 'B%'</tt>. Record creations from
+ # the association are scoped if a hash is used.
+ # <tt>has_many :posts, :conditions => {:published => true}</tt> will create published
+ # posts with <tt>@blog.posts.create</tt> or <tt>@blog.posts.build</tt>.
# [:order]
# Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
# such as <tt>last_name, first_name DESC</tt>.
# [:foreign_key]
# Specify the foreign key used for the association. By default this is guessed to be the name
- # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+ association will use "person_id"
- # as the default <tt>:foreign_key</tt>.
+ # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+
+ # association will use "person_id" as the default <tt>:foreign_key</tt>.
# [:primary_key]
# Specify the method that returns the primary key used for the association. By default this is +id+.
# [:dependent]
@@ -854,10 +906,12 @@ module ActiveRecord
#
# [:finder_sql]
# Specify a complete SQL statement to fetch the association. This is a good way to go for complex
- # associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added.
+ # associations that depend on multiple tables. Note: When this option is used, +find_in_collection+
+ # is _not_ added.
# [:counter_sql]
# Specify a complete SQL statement to fetch the size of the association. If <tt>:finder_sql</tt> is
- # specified but not <tt>:counter_sql</tt>, <tt>:counter_sql</tt> will be generated by replacing <tt>SELECT ... FROM</tt> with <tt>SELECT COUNT(*) FROM</tt>.
+ # specified but not <tt>:counter_sql</tt>, <tt>:counter_sql</tt> will be generated by
+ # replacing <tt>SELECT ... FROM</tt> with <tt>SELECT COUNT(*) FROM</tt>.
# [:extend]
# Specify a named module for extending the proxy. See "Association extensions".
# [:include]
@@ -865,25 +919,31 @@ module ActiveRecord
# [:group]
# An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
# [:having]
- # Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
+ # Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt>
+ # returns. Uses the <tt>HAVING</tt> SQL-clause.
# [:limit]
# An integer determining the limit on the number of rows that should be returned.
# [:offset]
- # An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
+ # An integer determining the offset from where the rows should be fetched. So at 5,
+ # it would skip the first 4 rows.
# [:select]
- # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if you, for example, want to do a join
- # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
+ # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if
+ # you, for example, want to do a join but not include the joined columns. Do not forget
+ # to include the primary and foreign keys, otherwise it will raise an error.
# [:as]
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
# [:through]
- # Specifies a join model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
- # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
- # <tt>has_one</tt> or <tt>has_many</tt> association on the join model. The collection of join models can be managed via the collection
- # API. For example, new join models are created for newly associated objects, and if some are gone their rows are deleted (directly,
+ # Specifies a join model through which to perform the query. Options for <tt>:class_name</tt>
+ # and <tt>:foreign_key</tt> are ignored, as the association uses the source reflection. You
+ # can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>, <tt>has_one</tt>
+ # or <tt>has_many</tt> association on the join model. The collection of join models
+ # can be managed via the collection API. For example, new join models are created for
+ # newly associated objects, and if some are gone their rows are deleted (directly,
# no destroy callbacks are triggered).
# [:source]
- # Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
- # inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or
+ # Specifies the source association name used by <tt>has_many :through</tt> queries.
+ # Only use it if the name cannot be inferred from the association.
+ # <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or
# <tt>:subscriber</tt> on Subscription, unless a <tt>:source</tt> is given.
# [:source_type]
# Specifies type of the source association used by <tt>has_many :through</tt> queries where the source
@@ -895,12 +955,14 @@ module ActiveRecord
# [:validate]
# If false, don't validate the associated objects when saving the parent object. true by default.
# [:autosave]
- # If true, always save the associated objects or destroy them if marked for destruction, when saving the parent object.
+ # If true, always save the associated objects or destroy them if marked for destruction,
+ # when saving the parent object.
# If false, never save or destroy the associated objects.
# By default, only save associated objects that are new records.
# [:inverse_of]
- # Specifies the name of the <tt>belongs_to</tt> association on the associated object that is the inverse of this <tt>has_many</tt>
- # association. Does not work in combination with <tt>:through</tt> or <tt>:as</tt> options.
+ # Specifies the name of the <tt>belongs_to</tt> association on the associated object
+ # that is the inverse of this <tt>has_many</tt> association. Does not work in combination
+ # with <tt>:through</tt> or <tt>:as</tt> options.
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
#
# Option examples:
@@ -974,19 +1036,20 @@ module ActiveRecord
# [:conditions]
# Specify the conditions that the associated object must meet in order to be included as a +WHERE+
# SQL fragment, such as <tt>rank = 5</tt>. Record creation from the association is scoped if a hash
- # is used. <tt>has_one :account, :conditions => {:enabled => true}</tt> will create an enabled account with <tt>@company.create_account</tt>
- # or <tt>@company.build_account</tt>.
+ # is used. <tt>has_one :account, :conditions => {:enabled => true}</tt> will create
+ # an enabled account with <tt>@company.create_account</tt> or <tt>@company.build_account</tt>.
# [:order]
# Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
# such as <tt>last_name, first_name DESC</tt>.
# [:dependent]
# If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
- # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. If set to <tt>:nullify</tt>, the associated
- # object's foreign key is set to +NULL+. Also, association is assigned.
+ # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
+ # If set to <tt>:nullify</tt>, the associated object's foreign key is set to +NULL+.
+ # Also, association is assigned.
# [:foreign_key]
# Specify the foreign key used for the association. By default this is guessed to be the name
- # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association will use "person_id"
- # as the default <tt>:foreign_key</tt>.
+ # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association
+ # will use "person_id" as the default <tt>:foreign_key</tt>.
# [:primary_key]
# Specify the method that returns the primary key used for the association. By default this is +id+.
# [:include]
@@ -994,15 +1057,18 @@ module ActiveRecord
# [:as]
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
# [:select]
- # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join
- # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
+ # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example,
+ # you want to do a join but not include the joined columns. Do not forget to include the
+ # primary and foreign keys, otherwise it will raise an error.
# [:through]
- # Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
- # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a
- # <tt>has_one</tt> or <tt>belongs_to</tt> association on the join model.
+ # Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt>
+ # and <tt>:foreign_key</tt> are ignored, as the association uses the source reflection. You
+ # can only use a <tt>:through</tt> query through a <tt>has_one</tt> or <tt>belongs_to</tt>
+ # association on the join model.
# [:source]
- # Specifies the source association name used by <tt>has_one :through</tt> queries. Only use it if the name cannot be
- # inferred from the association. <tt>has_one :favorite, :through => :favorites</tt> will look for a
+ # Specifies the source association name used by <tt>has_one :through</tt> queries.
+ # Only use it if the name cannot be inferred from the association.
+ # <tt>has_one :favorite, :through => :favorites</tt> will look for a
# <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given.
# [:source_type]
# Specifies type of the source association used by <tt>has_one :through</tt> queries where the source
@@ -1012,17 +1078,19 @@ module ActiveRecord
# [:validate]
# If false, don't validate the associated object when saving the parent object. +false+ by default.
# [:autosave]
- # If true, always save the associated object or destroy it if marked for destruction, when saving the parent object.
- # If false, never save or destroy the associated object.
+ # If true, always save the associated object or destroy it if marked for destruction,
+ # when saving the parent object. If false, never save or destroy the associated object.
# By default, only save the associated object if it's a new record.
# [:inverse_of]
- # Specifies the name of the <tt>belongs_to</tt> association on the associated object that is the inverse of this <tt>has_one</tt>
- # association. Does not work in combination with <tt>:through</tt> or <tt>:as</tt> options.
+ # Specifies the name of the <tt>belongs_to</tt> association on the associated object
+ # that is the inverse of this <tt>has_one</tt> association. Does not work in combination
+ # with <tt>:through</tt> or <tt>:as</tt> options.
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
#
# Option examples:
# has_one :credit_card, :dependent => :destroy # destroys the associated credit card
- # has_one :credit_card, :dependent => :nullify # updates the associated records foreign key value to NULL rather than destroying it
+ # has_one :credit_card, :dependent => :nullify # updates the associated records foreign
+ # # key value to NULL rather than destroying it
# has_one :last_comment, :class_name => "Comment", :order => "posted_on"
# has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'"
# has_one :attachment, :as => :attachable
@@ -1084,27 +1152,34 @@ module ActiveRecord
# Specify the conditions that the associated object must meet in order to be included as a +WHERE+
# SQL fragment, such as <tt>authorized = 1</tt>.
# [:select]
- # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join
- # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
+ # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed
+ # if, for example, you want to do a join but not include the joined columns. Do not
+ # forget to include the primary and foreign keys, otherwise it will raise an error.
# [:foreign_key]
# Specify the foreign key used for the association. By default this is guessed to be the name
- # of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt> association will use
- # "person_id" as the default <tt>:foreign_key</tt>. Similarly, <tt>belongs_to :favorite_person, :class_name => "Person"</tt>
- # will use a foreign key of "favorite_person_id".
+ # of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt>
+ # association will use "person_id" as the default <tt>:foreign_key</tt>. Similarly,
+ # <tt>belongs_to :favorite_person, :class_name => "Person"</tt> will use a foreign key
+ # of "favorite_person_id".
# [:primary_key]
- # Specify the method that returns the primary key of associated object used for the association. By default this is id.
+ # Specify the method that returns the primary key of associated object used for the association.
+ # By default this is id.
# [:dependent]
# If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
- # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. This option should not be specified when
- # <tt>belongs_to</tt> is used in conjunction with a <tt>has_many</tt> relationship on another class because of the potential to leave
+ # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
+ # This option should not be specified when <tt>belongs_to</tt> is used in conjunction with
+ # a <tt>has_many</tt> relationship on another class because of the potential to leave
# orphaned records behind.
# [:counter_cache]
# Caches the number of belonging objects on the associate class through the use of +increment_counter+
- # and +decrement_counter+. The counter cache is incremented when an object of this class is created and decremented when it's
- # destroyed. This requires that a column named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class)
- # is used on the associate class (such as a Post class). You can also specify a custom counter cache column by providing
- # a column name instead of a +true+/+false+ value to this option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
- # Note: Specifying a counter cache will add it to that model's list of readonly attributes using +attr_readonly+.
+ # and +decrement_counter+. The counter cache is incremented when an object of this
+ # class is created and decremented when it's destroyed. This requires that a column
+ # named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class)
+ # is used on the associate class (such as a Post class). You can also specify a custom counter
+ # cache column by providing a column name instead of a +true+/+false+ value to this
+ # option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
+ # Note: Specifying a counter cache will add it to that model's list of readonly attributes
+ # using +attr_readonly+.
# [:include]
# Specify second-order associations that should be eager loaded when this object is loaded.
# [:polymorphic]
@@ -1116,15 +1191,18 @@ module ActiveRecord
# [:validate]
# If false, don't validate the associated objects when saving the parent object. +false+ by default.
# [:autosave]
- # If true, always save the associated object or destroy it if marked for destruction, when saving the parent object.
+ # If true, always save the associated object or destroy it if marked for destruction, when
+ # saving the parent object.
# If false, never save or destroy the associated object.
# By default, only save the associated object if it's a new record.
# [: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.
+ # 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.
# [: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 combination with the <tt>:polymorphic</tt> options.
+ # 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
+ # combination with the <tt>:polymorphic</tt> options.
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
#
# Option examples:
@@ -1158,9 +1236,10 @@ module ActiveRecord
# Specifies a many-to-many relationship with another class. This associates two classes via an
# intermediate join table. Unless the join table is explicitly specified as an option, it is
# guessed using the lexical order of the class names. So a join between Developer and Project
- # will give the default join table name of "developers_projects" because "D" outranks "P". Note that this precedence
- # is calculated using the <tt><</tt> operator for String. This means that if the strings are of different lengths,
- # and the strings are equal when compared up to the shortest length, then the longer string is considered of higher
+ # will give the default join table name of "developers_projects" because "D" outranks "P".
+ # Note that this precedence is calculated using the <tt><</tt> operator for String. This
+ # means that if the strings are of different lengths, and the strings are equal when compared
+ # up to the shortest length, then the longer string is considered of higher
# lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers"
# to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes",
# but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the
@@ -1182,9 +1261,10 @@ module ActiveRecord
# end
# end
#
- # Deprecated: Any additional fields added to the join table will be placed as attributes when pulling records out through
- # +has_and_belongs_to_many+ associations. Records returned from join tables with additional attributes will be marked as
- # readonly (because we can't save changes to the additional attributes). It's strongly recommended that you upgrade any
+ # Deprecated: Any additional fields added to the join table will be placed as attributes when
+ # pulling records out through +has_and_belongs_to_many+ associations. Records returned from join
+ # tables with additional attributes will be marked as readonly (because we can't save changes
+ # to the additional attributes). It's strongly recommended that you upgrade any
# associations with attributes to a real join model (see introduction).
#
# Adds the following methods for retrieval and query:
@@ -1224,7 +1304,8 @@ module ActiveRecord
# with +attributes+ and linked to this object through the join table, but has not yet been saved.
# [collection.create(attributes = {})]
# Returns a new object of the collection type that has been instantiated
- # with +attributes+, linked to this object through the join table, and that has already been saved (if it passed the validation).
+ # with +attributes+, linked to this object through the join table, and that has already been
+ # saved (if it passed the validation).
#
# (+collection+ is replaced with the symbol passed as the first argument, so
# <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.)
@@ -1259,8 +1340,9 @@ module ActiveRecord
# MUST be declared underneath any +has_and_belongs_to_many+ declaration in order to work.
# [:foreign_key]
# Specify the foreign key used for the association. By default this is guessed to be the name
- # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_and_belongs_to_many+ association
- # to Project will use "person_id" as the default <tt>:foreign_key</tt>.
+ # of this class in lower-case and "_id" suffixed. So a Person class that makes
+ # a +has_and_belongs_to_many+ association to Project will use "person_id" as the
+ # default <tt>:foreign_key</tt>.
# [:association_foreign_key]
# Specify the foreign key used for the association on the receiving side of the association.
# By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed.
@@ -1268,7 +1350,8 @@ module ActiveRecord
# the association will use "project_id" as the default <tt>:association_foreign_key</tt>.
# [:conditions]
# Specify the conditions that the associated object must meet in order to be included as a +WHERE+
- # SQL fragment, such as <tt>authorized = 1</tt>. Record creations from the association are scoped if a hash is used.
+ # SQL fragment, such as <tt>authorized = 1</tt>. Record creations from the association are
+ # scoped if a hash is used.
# <tt>has_many :posts, :conditions => {:published => true}</tt> will create published posts with <tt>@blog.posts.create</tt>
# or <tt>@blog.posts.build</tt>.
# [:order]
@@ -1280,7 +1363,8 @@ module ActiveRecord
# Overwrite the default generated SQL statement used to fetch the association with a manual statement
# [:counter_sql]
# Specify a complete SQL statement to fetch the size of the association. If <tt>:finder_sql</tt> is
- # specified but not <tt>:counter_sql</tt>, <tt>:counter_sql</tt> will be generated by replacing <tt>SELECT ... FROM</tt> with <tt>SELECT COUNT(*) FROM</tt>.
+ # specified but not <tt>:counter_sql</tt>, <tt>:counter_sql</tt> will be generated by
+ # replacing <tt>SELECT ... FROM</tt> with <tt>SELECT COUNT(*) FROM</tt>.
# [:delete_sql]
# Overwrite the default generated SQL statement used to remove links between the associated
# classes with a manual statement.
@@ -1294,20 +1378,24 @@ module ActiveRecord
# [:group]
# An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
# [:having]
- # Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
+ # Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns.
+ # Uses the <tt>HAVING</tt> SQL-clause.
# [:limit]
# An integer determining the limit on the number of rows that should be returned.
# [:offset]
- # An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
+ # An integer determining the offset from where the rows should be fetched. So at 5,
+ # it would skip the first 4 rows.
# [:select]
- # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join
- # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
+ # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example,
+ # you want to do a join but not include the joined columns. Do not forget to include the primary
+ # and foreign keys, otherwise it will raise an error.
# [:readonly]
# If true, all the associated objects are readonly through the association.
# [:validate]
# If false, don't validate the associated objects when saving the parent object. +true+ by default.
# [:autosave]
- # If true, always save the associated objects or destroy them if marked for destruction, when saving the parent object.
+ # If true, always save the associated objects or destroy them if marked for destruction, when
+ # saving the parent object.
# If false, never save or destroy the associated objects.
# By default, only save associated objects that are new records.
#
@@ -1354,7 +1442,7 @@ module ActiveRecord
end
def association_accessor_methods(reflection, association_proxy_class)
- define_method(reflection.name) do |*params|
+ redefine_method(reflection.name) do |*params|
force_reload = params.first unless params.empty?
association = association_instance_get(reflection.name)
@@ -1371,12 +1459,12 @@ module ActiveRecord
association.target.nil? ? nil : association
end
- define_method("loaded_#{reflection.name}?") do
+ redefine_method("loaded_#{reflection.name}?") do
association = association_instance_get(reflection.name)
association && association.loaded?
end
-
- define_method("#{reflection.name}=") do |new_value|
+
+ redefine_method("#{reflection.name}=") do |new_value|
association = association_instance_get(reflection.name)
if association.nil? || association.target != new_value
@@ -1386,8 +1474,8 @@ module ActiveRecord
association.replace(new_value)
association_instance_set(reflection.name, new_value.nil? ? nil : association)
end
-
- define_method("set_#{reflection.name}_target") do |target|
+
+ redefine_method("set_#{reflection.name}_target") do |target|
return if target.nil? and association_proxy_class == BelongsToAssociation
association = association_proxy_class.new(self, reflection)
association.target = target
@@ -1396,7 +1484,7 @@ module ActiveRecord
end
def collection_reader_method(reflection, association_proxy_class)
- define_method(reflection.name) do |*params|
+ redefine_method(reflection.name) do |*params|
force_reload = params.first unless params.empty?
association = association_instance_get(reflection.name)
@@ -1409,8 +1497,8 @@ module ActiveRecord
association
end
-
- define_method("#{reflection.name.to_s.singularize}_ids") do
+
+ redefine_method("#{reflection.name.to_s.singularize}_ids") do
if send(reflection.name).loaded? || reflection.options[:finder_sql]
send(reflection.name).map(&:id)
else
@@ -1430,22 +1518,24 @@ module ActiveRecord
collection_reader_method(reflection, association_proxy_class)
if writer
- define_method("#{reflection.name}=") do |new_value|
+ redefine_method("#{reflection.name}=") do |new_value|
# Loads proxy class instance (defined in collection_reader_method) if not already loaded
association = send(reflection.name)
association.replace(new_value)
association
end
- define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
- ids = (new_value || []).reject { |nid| nid.blank? }.map(&:to_i)
+ redefine_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
+ pk_column = reflection.primary_key_column
+ ids = (new_value || []).reject { |nid| nid.blank? }
+ ids.map!{ |i| pk_column.type_cast(i) }
send("#{reflection.name}=", reflection.klass.find(ids).index_by(&:id).values_at(*ids))
end
end
end
def association_constructor_method(constructor, reflection, association_proxy_class)
- define_method("#{constructor}_#{reflection.name}") do |*params|
+ redefine_method("#{constructor}_#{reflection.name}") do |*params|
attributees = params.first unless params.empty?
replace_existing = params[1].nil? ? true : params[1]
association = association_instance_get(reflection.name)
@@ -1486,8 +1576,8 @@ module ActiveRecord
end
def add_touch_callbacks(reflection, touch_attribute)
- method_name = "belongs_to_touch_after_save_or_destroy_for_#{reflection.name}".to_sym
- define_method(method_name) do
+ method_name = :"belongs_to_touch_after_save_or_destroy_for_#{reflection.name}"
+ redefine_method(method_name) do
association = send(reflection.name)
if touch_attribute == true
@@ -1497,20 +1587,18 @@ module ActiveRecord
end
end
after_save(method_name)
+ after_touch(method_name)
after_destroy(method_name)
end
# Creates before_destroy callback methods that nullify, delete or destroy
# has_many associated objects, according to the defined :dependent rule.
- # If the association is marked as :dependent => :restrict, create a callback
- # that prevents deleting entirely.
#
- # See HasManyAssociation#delete_records. Dependent associations
- # delete children, otherwise foreign key is set to NULL.
- # See HasManyAssociation#delete_records. Dependent associations
- # delete children if the option is set to :destroy or :delete_all, set the
- # foreign key to NULL if the option is set to :nullify, and do not touch the
- # child records if the option is set to :restrict.
+ # See HasManyAssociation#delete_records for more information. In general
+ # - delete children if the option is set to :destroy or :delete_all
+ # - set the foreign key to NULL if the option is set to :nullify
+ # - do not delete the parent record if there is any child record if the
+ # option is set to :restrict
#
# The +extra_conditions+ parameter, which is not used within the main
# Active Record codebase, is meant to allow plugins to define extra
@@ -1761,7 +1849,7 @@ module ActiveRecord
def graft(*associations)
associations.each do |association|
join_associations.detect {|a| association == a} ||
- build(association.reflection.name, association.find_parent_in(self), association.join_class)
+ build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_class)
end
self
end
@@ -1801,9 +1889,7 @@ module ActiveRecord
case associations
when Symbol, String
reflection = base.reflections[associations]
- if reflection && reflection.collection?
- records.each { |record| record.send(reflection.name).target.uniq! }
- end
+ remove_uniq_by_reflection(reflection, records)
when Array
associations.each do |association|
remove_duplicate_results!(base, records, association)
@@ -1811,6 +1897,7 @@ module ActiveRecord
when Hash
associations.keys.each do |name|
reflection = base.reflections[name]
+ remove_uniq_by_reflection(reflection, records)
parent_records = []
records.each do |record|
@@ -1829,6 +1916,7 @@ module ActiveRecord
end
protected
+
def build(associations, parent = nil, join_class = Arel::InnerJoin)
parent ||= @joins.last
case associations
@@ -1851,6 +1939,12 @@ module ActiveRecord
end
end
+ def remove_uniq_by_reflection(reflection, records)
+ if reflection && reflection.collection?
+ records.each { |record| record.send(reflection.name).target.uniq! }
+ end
+ end
+
def build_join_association(reflection, parent)
JoinAssociation.new(reflection, self, parent)
end
@@ -1965,7 +2059,7 @@ module ActiveRecord
end
class JoinAssociation < JoinBase # :nodoc:
- attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name
+ attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name, :join_class
delegate :options, :klass, :through_reflection, :source_reflection, :to => :reflection
def initialize(reflection, join_dependency, parent = nil)
@@ -1982,6 +2076,7 @@ module ActiveRecord
@parent_table_name = parent.active_record.table_name
@aliased_table_name = aliased_table_name_for(table_name)
@join = nil
+ @join_class = Arel::InnerJoin
if reflection.macro == :has_and_belongs_to_many
@aliased_join_table_name = aliased_table_name_for(reflection.options[:join_table], "_join")
@@ -2004,10 +2099,6 @@ module ActiveRecord
end
end
- def join_class
- @join_class ||= Arel::InnerJoin
- end
-
def with_join_class(join_class)
@join_class = join_class
self
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index 615b7d2719..b5159eead3 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -183,10 +183,13 @@ module ActiveRecord
# descendant's +construct_sql+ method will have set :counter_sql automatically.
# Otherwise, construct options and pass them with scope to the target class's +count+.
def count(column_name = nil, options = {})
- if @reflection.options[:counter_sql]
+ column_name, options = nil, column_name if column_name.is_a?(Hash)
+
+ if @reflection.options[:counter_sql] && !options.blank?
+ raise ArgumentError, "If finder_sql/counter_sql is used then options cannot be passed"
+ elsif @reflection.options[:counter_sql]
@reflection.klass.count_by_sql(@counter_sql)
else
- column_name, options = nil, column_name if column_name.is_a?(Hash)
if @reflection.options[:uniq]
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
@@ -215,9 +218,9 @@ module ActiveRecord
# are actually removed from the database, that depends precisely on
# +delete_records+. They are in any case removed from the collection.
def delete(*records)
- remove_records(records) do |records, old_records|
+ remove_records(records) do |_records, old_records|
delete_records(old_records) if old_records.any?
- records.each { |record| @target.delete(record) }
+ _records.each { |record| @target.delete(record) }
end
end
@@ -228,7 +231,7 @@ module ActiveRecord
# ignoring the +:dependent+ option.
def destroy(*records)
records = find(records) if records.any? {|record| record.kind_of?(Fixnum) || record.kind_of?(String)}
- remove_records(records) do |records, old_records|
+ remove_records(records) do |_records, old_records|
old_records.each { |record| record.destroy }
end
@@ -393,11 +396,12 @@ module ActiveRecord
if @target.is_a?(Array) && @target.any?
@target = find_target.map do |f|
i = @target.index(f)
- t = @target.delete_at(i) if i
- if t && t.changed?
- t
+ if i
+ @target.delete_at(i).tap do |t|
+ keys = ["id"] + t.changes.keys + (f.attribute_names - t.attribute_names)
+ t.attributes = f.attributes.except(*keys)
+ end
else
- f.mark_for_destruction if t && t.marked_for_destruction?
f
end
end + @target
@@ -415,15 +419,10 @@ module ActiveRecord
end
def method_missing(method, *args)
- case method.to_s
- when 'find_or_create'
- return find(:first, :conditions => args.first) || create(args.first)
- when /^find_or_create_by_(.*)$/
- rest = $1
- return send("find_by_#{rest}", *args) ||
- method_missing("create_by_#{rest}", *args)
- when /^create_by_(.*)$/
- return create Hash[$1.split('_and_').zip(args)]
+ match = DynamicFinderMatch.match(method)
+ if match && match.creator?
+ attributes = match.attribute_names
+ return send(:"find_by_#{attributes.join('_and_')}", *args) || create(Hash[attributes.zip(args)])
end
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
@@ -479,7 +478,11 @@ module ActiveRecord
callback(:before_add, record)
yield(record) if block_given?
@target ||= [] unless loaded?
- @target << record unless @reflection.options[:uniq] && @target.include?(record)
+ if index = @target.index(record)
+ @target[index] = record
+ else
+ @target << record
+ end
callback(:after_add, record)
set_inverse_instance(record, @owner)
record
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index c2a6495db5..4558872a2b 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -22,7 +22,7 @@ module ActiveRecord
else
raise_on_type_mismatch(record)
- if counter_cache_name && !@owner.new_record?
+ if counter_cache_name && !@owner.new_record? && record.id != @owner[@reflection.primary_key_name]
@reflection.klass.increment_counter(counter_cache_name, record.id)
@reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
end
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index c989c3536d..bec123e7a2 100644
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -45,17 +45,23 @@ module ActiveRecord
if @reflection.options[:insert_sql]
@owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
else
- relation = Arel::Table.new(@reflection.options[:join_table])
+ relation = Arel::Table.new(@reflection.options[:join_table])
+ timestamps = record_timestamp_columns(record)
+ timezone = record.send(:current_time_from_proper_timezone) if timestamps.any?
+
attributes = columns.inject({}) do |attrs, column|
- case column.name.to_s
+ name = column.name
+ case name.to_s
when @reflection.primary_key_name.to_s
- attrs[relation[column.name]] = owner_quoted_id
+ attrs[relation[name]] = @owner.id
when @reflection.association_foreign_key.to_s
- attrs[relation[column.name]] = record.quoted_id
+ attrs[relation[name]] = record.id
+ when *timestamps
+ attrs[relation[name]] = timezone
else
- if record.has_attribute?(column.name)
- value = @owner.send(:quote_value, record[column.name], column)
- attrs[relation[column.name]] = value unless value.nil?
+ if record.has_attribute?(name)
+ value = @owner.send(:quote_value, record[name], column)
+ attrs[relation[name]] = value unless value.nil?
end
end
attrs
@@ -100,9 +106,10 @@ module ActiveRecord
:limit => @reflection.options[:limit] } }
end
- # Join tables with additional columns on top of the two foreign keys must be considered ambiguous unless a select
- # clause has been explicitly defined. Otherwise you can get broken records back, if, for example, the join column also has
- # an id column. This will then overwrite the id column of the records coming back.
+ # Join tables with additional columns on top of the two foreign keys must be considered
+ # ambiguous unless a select clause has been explicitly defined. Otherwise you can get
+ # broken records back, if, for example, the join column also has an id column. This will
+ # then overwrite the id column of the records coming back.
def finding_with_ambiguous_select?(select_clause)
!select_clause && columns.size != 2
end
@@ -117,6 +124,14 @@ module ActiveRecord
build_record(attributes, &block)
end
end
+
+ def record_timestamp_columns(record)
+ if record.record_timestamps
+ record.send(:all_timestamp_attributes).map(&:to_s)
+ else
+ []
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index d74fb7c702..c33bc6aa47 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -24,7 +24,7 @@ module ActiveRecord
# If the association has a counter cache it gets that value. Otherwise
# it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if
# there's one. Some configuration options like :group make it impossible
- # to do a SQL count, in those cases the array count will be used.
+ # to do an SQL count, in those cases the array count will be used.
#
# That does not depend on whether the collection has already been loaded
# or not. The +size+ method is the one that takes the loaded flag into
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 17f850756f..608b1c741a 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -24,9 +24,10 @@ module ActiveRecord
end
end
- # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
- # calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero,
- # and you need to fetch that collection afterwards, it'll take one fewer SELECT query if you use #length.
+ # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been
+ # loaded and calling collection.size if it has. If it's more likely than not that the collection does
+ # have a size larger than zero, and you need to fetch that collection afterwards, it'll take one fewer
+ # SELECT query if you use #length.
def size
return @owner.send(:read_attribute, cached_counter_attribute_name) if has_cached_counter?
return @target.size if loaded?
diff --git a/activerecord/lib/active_record/associations/through_association_scope.rb b/activerecord/lib/active_record/associations/through_association_scope.rb
index 22e1033a9d..cabb33c4a8 100644
--- a/activerecord/lib/active_record/associations/through_association_scope.rb
+++ b/activerecord/lib/active_record/associations/through_association_scope.rb
@@ -35,7 +35,7 @@ module ActiveRecord
@owner.class.base_class.name.to_s,
reflection.klass.columns_hash["#{as}_type"]) }
elsif reflection.macro == :belongs_to
- { reflection.klass.primary_key => @owner[reflection.primary_key_name] }
+ { reflection.klass.primary_key => @owner.class.quote_value(@owner[reflection.primary_key_name]) }
else
{ reflection.primary_key_name => owner_quoted_id }
end
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index 783d61383b..8f0aacba42 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -14,7 +14,8 @@ module ActiveRecord
module ClassMethods
protected
# Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
- # This enhanced read method automatically converts the UTC time stored in the database to the time zone stored in Time.zone.
+ # This enhanced read method automatically converts the UTC time stored in the database to the time
+ # zone stored in Time.zone.
def define_method_attribute(attr_name)
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
method_body, line = <<-EOV, __LINE__ + 1
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index e31acac050..7a2de3bf80 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -14,8 +14,8 @@ module ActiveRecord
end
end
- # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float
- # columns are turned into +nil+.
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings
+ # for fixnum and float columns are turned into +nil+.
def write_attribute(attr_name, value)
attr_name = attr_name.to_s
attr_name = self.class.primary_key if attr_name == 'id'
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 7517896235..2c7afe3c9f 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -4,14 +4,13 @@ module ActiveRecord
# = Active Record Autosave Association
#
# AutosaveAssociation is a module that takes care of automatically saving
- # your associations when the parent is saved. In addition to saving, it
- # also destroys any associations that were marked for destruction.
+ # associacted records when parent is saved. In addition to saving, it
+ # also destroys any associated records that were marked for destruction.
# (See mark_for_destruction and marked_for_destruction?)
#
# Saving of the parent, its associations, and the destruction of marked
# associations, all happen inside 1 transaction. This should never leave the
- # database in an inconsistent state after, for instance, mass assigning
- # attributes and saving them.
+ # database in an inconsistent state.
#
# If validations for any of the associations fail, their error messages will
# be applied to the parent.
@@ -21,8 +20,6 @@ module ActiveRecord
#
# === One-to-one Example
#
- # Consider a Post model with one Author:
- #
# class Post
# has_one :author, :autosave => true
# end
@@ -155,11 +152,12 @@ module ActiveRecord
CODE
end
- # Adds a validate and save callback for the association as specified by
+ # Adds validation and save callbacks for the association as specified by
# the +reflection+.
#
- # For performance reasons, we don't check whether to validate at runtime,
- # but instead only define the method and callback when needed. However,
+ # For performance reasons, we don't check whether to validate at runtime.
+ # However the validation and callback methods are lazy and those methods
+ # get created when they are invoked for the very first time. However,
# this can change, for instance, when using nested attributes, which is
# called _after_ the association has been defined. Since we don't want
# the callbacks to get defined multiple times, there are guards that
@@ -197,14 +195,15 @@ module ActiveRecord
end
end
- # Reloads the attributes of the object as usual and removes a mark for destruction.
+ # Reloads the attributes of the object as usual and clears <tt>marked_for_destruction</tt> flag.
def reload(options = nil)
@marked_for_destruction = false
super
end
# Marks this record to be destroyed as part of the parents save transaction.
- # This does _not_ actually destroy the record yet, rather it will be destroyed when <tt>parent.save</tt> is called.
+ # This does _not_ actually destroy the record instantly, rather child record will be destroyed
+ # when <tt>parent.save</tt> is called.
#
# Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
def mark_for_destruction
@@ -249,7 +248,7 @@ module ActiveRecord
end
# Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
- # turned on for the association specified by +reflection+.
+ # turned on for the association.
def validate_single_association(reflection)
if (association = association_instance_get(reflection.name)) && !association.target.nil?
association_valid?(reflection, association)
@@ -357,14 +356,9 @@ module ActiveRecord
end
end
- # Saves the associated record if it's new or <tt>:autosave</tt> is enabled
- # on the association.
+ # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
#
- # In addition, it will destroy the association if it was marked for
- # destruction with mark_for_destruction.
- #
- # This all happens inside a transaction, _if_ the Transactions module is included into
- # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
+ # In addition, it will destroy the association if it was marked for destruction.
def save_belongs_to_association(reflection)
if (association = association_instance_get(reflection.name)) && !association.destroyed?
autosave = reflection.options[:autosave]
@@ -377,10 +371,6 @@ module ActiveRecord
if association.updated?
association_id = association.send(reflection.options[:primary_key] || :id)
self[reflection.primary_key_name] = association_id
- # TODO: Removing this code doesn't seem to matter...
- if reflection.options[:polymorphic]
- self[reflection.options[:foreign_type]] = association.class.base_class.name.to_s
- end
end
saved if autosave
@@ -388,4 +378,4 @@ module ActiveRecord
end
end
end
-end \ No newline at end of file
+end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index c78060c956..8da4fbcba7 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -26,17 +26,19 @@ require 'active_record/log_subscriber'
module ActiveRecord #:nodoc:
# = Active Record
#
- # Active Record objects don't specify their attributes directly, but rather infer them from the table definition with
- # which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
- # is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain
+ # Active Record objects don't specify their attributes directly, but rather infer them from
+ # the table definition with which they're linked. Adding, removing, and changing attributes
+ # and their type is done directly in the database. Any change is instantly reflected in the
+ # Active Record objects. The mapping that binds a given Active Record class to a certain
# database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
#
# See the mapping rules in table_name and the full example in link:files/README.html for more insight.
#
# == Creation
#
- # Active Records accept constructor parameters either in a hash or as a block. The hash method is especially useful when
- # you're receiving the data from somewhere else, like an HTTP request. It works like this:
+ # Active Records accept constructor parameters either in a hash or as a block. The hash
+ # method is especially useful when you're receiving the data from somewhere else, like an
+ # HTTP request. It works like this:
#
# user = User.new(:name => "David", :occupation => "Code Artist")
# user.name # => "David"
@@ -75,14 +77,17 @@ module ActiveRecord #:nodoc:
# end
# end
#
- # The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query and is thus susceptible to SQL-injection
- # attacks if the <tt>user_name</tt> and +password+ parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and
- # <tt>authenticate_safely_simply</tt> both will sanitize the <tt>user_name</tt> and +password+ before inserting them in the query,
- # which will ensure that an attacker can't escape the query and fake the login (or worse).
+ # The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query
+ # and is thus susceptible to SQL-injection attacks if the <tt>user_name</tt> and +password+
+ # parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and
+ # <tt>authenticate_safely_simply</tt> both will sanitize the <tt>user_name</tt> and +password+
+ # before inserting them in the query, which will ensure that an attacker can't escape the
+ # query and fake the login (or worse).
#
- # When using multiple parameters in the conditions, it can easily become hard to read exactly what the fourth or fifth
- # question mark is supposed to represent. In those cases, you can resort to named bind variables instead. That's done by replacing
- # the question marks with symbols and supplying a hash with values for the matching symbol keys:
+ # When using multiple parameters in the conditions, it can easily become hard to read exactly
+ # what the fourth or fifth question mark is supposed to represent. In those cases, you can
+ # resort to named bind variables instead. That's done by replacing the question marks with
+ # symbols and supplying a hash with values for the matching symbol keys:
#
# Company.where(
# "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
@@ -103,18 +108,19 @@ module ActiveRecord #:nodoc:
#
# Student.where(:grade => [9,11,12])
#
- # When joining tables, nested hashes or keys written in the form 'table_name.column_name' can be used to qualify the table name of a
- # particular condition. For instance:
+ # When joining tables, nested hashes or keys written in the form 'table_name.column_name'
+ # can be used to qualify the table name of a particular condition. For instance:
#
# Student.joins(:schools).where(:schools => { :type => 'public' })
# Student.joins(:schools).where('schools.type' => 'public' )
#
# == Overwriting default accessors
#
- # All column values are automatically available through basic accessors on the Active Record object, but sometimes you
- # want to specialize this behavior. This can be done by overwriting the default accessors (using the same
- # name as the attribute) and calling <tt>read_attribute(attr_name)</tt> and <tt>write_attribute(attr_name, value)</tt> to actually change things.
- # Example:
+ # All column values are automatically available through basic accessors on the Active Record
+ # object, but sometimes you want to specialize this behavior. This can be done by overwriting
+ # the default accessors (using the same name as the attribute) and calling
+ # <tt>read_attribute(attr_name)</tt> and <tt>write_attribute(attr_name, value)</tt> to actually
+ # change things.
#
# class Song < ActiveRecord::Base
# # Uses an integer of seconds to hold the length of the song
@@ -128,8 +134,8 @@ module ActiveRecord #:nodoc:
# end
# end
#
- # You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt> instead of <tt>write_attribute(:attribute, value)</tt> and
- # <tt>read_attribute(:attribute)</tt> as a shorter form.
+ # You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt>
+ # instead of <tt>write_attribute(:attribute, value)</tt> and <tt>read_attribute(:attribute)</tt>.
#
# == Attribute query methods
#
@@ -147,34 +153,43 @@ module ActiveRecord #:nodoc:
#
# == Accessing attributes before they have been typecasted
#
- # Sometimes you want to be able to read the raw attribute data without having the column-determined typecast run its course first.
- # That can be done by using the <tt><attribute>_before_type_cast</tt> accessors that all attributes have. For example, if your Account model
- # has a <tt>balance</tt> attribute, you can call <tt>account.balance_before_type_cast</tt> or <tt>account.id_before_type_cast</tt>.
+ # Sometimes you want to be able to read the raw attribute data without having the column-determined
+ # typecast run its course first. That can be done by using the <tt><attribute>_before_type_cast</tt>
+ # accessors that all attributes have. For example, if your Account model has a <tt>balance</tt> attribute,
+ # you can call <tt>account.balance_before_type_cast</tt> or <tt>account.id_before_type_cast</tt>.
#
- # This is especially useful in validation situations where the user might supply a string for an integer field and you want to display
- # the original string back in an error message. Accessing the attribute normally would typecast the string to 0, which isn't what you
- # want.
+ # This is especially useful in validation situations where the user might supply a string for an
+ # integer field and you want to display the original string back in an error message. Accessing the
+ # attribute normally would typecast the string to 0, which isn't what you want.
#
# == Dynamic attribute-based finders
#
- # Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects by simple queries without turning to SQL. They work by
- # appending the name of an attribute to <tt>find_by_</tt>, <tt>find_last_by_</tt>, or <tt>find_all_by_</tt>, so you get finders like <tt>Person.find_by_user_name</tt>,
- # <tt>Person.find_all_by_last_name</tt>, and <tt>Payment.find_by_transaction_id</tt>. So instead of writing
+ # Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects
+ # by simple queries without turning to SQL. They work by appending the name of an attribute
+ # to <tt>find_by_</tt>, <tt>find_last_by_</tt>, or <tt>find_all_by_</tt> and thus produces finders
+ # like <tt>Person.find_by_user_name</tt>, <tt>Person.find_all_by_last_name</tt>, and
+ # <tt>Payment.find_by_transaction_id</tt>. Instead of writing
# <tt>Person.where(:user_name => user_name).first</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
- # And instead of writing <tt>Person.where(:last_name => last_name).all</tt>, you just do <tt>Person.find_all_by_last_name(last_name)</tt>.
+ # And instead of writing <tt>Person.where(:last_name => last_name).all</tt>, you just do
+ # <tt>Person.find_all_by_last_name(last_name)</tt>.
#
- # It's also possible to use multiple attributes in the same find by separating them with "_and_", so you get finders like
- # <tt>Person.find_by_user_name_and_password</tt> or even <tt>Payment.find_by_purchaser_and_state_and_country</tt>. So instead of writing
- # <tt>Person.where(:user_name => user_name, :password => password).first</tt>, you just do
- # <tt>Person.find_by_user_name_and_password(user_name, password)</tt>.
+ # It's also possible to use multiple attributes in the same find by separating them with "_and_".
+ #
+ # Person.where(:user_name => user_name, :password => password).first
+ # Person.find_by_user_name_and_password #with dynamic finder
+ #
+ # Person.where(:user_name => user_name, :password => password, :gender => 'male').first
+ # Payment.find_by_user_name_and_password_and_gender
#
- # It's even possible to call these dynamic finder methods on relations and named scopes. For example :
+ # It's even possible to call these dynamic finder methods on relations and named scopes.
#
# Payment.order("created_on").find_all_by_amount(50)
# Payment.pending.find_last_by_amount(100)
#
- # The same dynamic finder style can be used to create the object if it doesn't already exist. This dynamic finder is called with
- # <tt>find_or_create_by_</tt> and will return the object if it already exists and otherwise creates it, then returns it. Protected attributes won't be set unless they are given in a block. For example:
+ # The same dynamic finder style can be used to create the object if it doesn't already exist.
+ # This dynamic finder is called with <tt>find_or_create_by_</tt> and will return the object if
+ # it already exists and otherwise creates it, then returns it. Protected attributes won't be set
+ # unless they are given in a block.
#
# # No 'Summer' tag exists
# Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
@@ -185,23 +200,33 @@ module ActiveRecord #:nodoc:
# # Now 'Bob' exist and is an 'admin'
# User.find_or_create_by_name('Bob', :age => 40) { |u| u.admin = true }
#
- # Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without saving it first. Protected attributes won't be set unless they are given in a block. For example:
+ # Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without
+ # saving it first. Protected attributes won't be set unless they are given in a block.
#
# # No 'Winter' tag exists
# winter = Tag.find_or_initialize_by_name("Winter")
# winter.new_record? # true
#
# To find by a subset of the attributes to be used for instantiating a new object, pass a hash instead of
- # a list of parameters. For example:
+ # a list of parameters.
#
# Tag.find_or_create_by_name(:name => "rails", :creator => current_user)
#
- # That will either find an existing tag named "rails", or create a new one while setting the user that created it.
+ # That will either find an existing tag named "rails", or create a new one while setting the
+ # user that created it.
+ #
+ # Just like <tt>find_by_*</tt>, you can also use <tt>scoped_by_*</tt> to retrieve data. The good thing about
+ # using this feature is that the very first time result is returned using <tt>method_missing</tt> technique
+ # but after that the method is declared on the class. Henceforth <tt>method_missing</tt> will not be hit.
+ #
+ # User.scoped_by_user_name('David')
#
# == Saving arrays, hashes, and other non-mappable objects in text columns
#
- # Active Record can serialize any object in text columns using YAML. To do so, you must specify this with a call to the class method +serialize+.
- # This makes it possible to store arrays, hashes, and other non-mappable objects without doing any additional work. Example:
+ # Active Record can serialize any object in text columns using YAML. To do so, you must
+ # specify this with a call to the class method +serialize+.
+ # This makes it possible to store arrays, hashes, and other non-mappable objects without doing
+ # any additional work.
#
# class User < ActiveRecord::Base
# serialize :preferences
@@ -210,8 +235,8 @@ module ActiveRecord #:nodoc:
# user = User.create(:preferences => { "background" => "black", "display" => large })
# User.find(user.id).preferences # => { "background" => "black", "display" => large }
#
- # You can also specify a class option as the second parameter that'll raise an exception if a serialized object is retrieved as a
- # descendant of a class not in the hierarchy. Example:
+ # You can also specify a class option as the second parameter that'll raise an exception
+ # if a serialized object is retrieved as a descendant of a class not in the hierarchy.
#
# class User < ActiveRecord::Base
# serialize :preferences, Hash
@@ -222,52 +247,63 @@ module ActiveRecord #:nodoc:
#
# == Single table inheritance
#
- # Active Record allows inheritance by storing the name of the class in a column that by default is named "type" (can be changed
- # by overwriting <tt>Base.inheritance_column</tt>). This means that an inheritance looking like this:
+ # Active Record allows inheritance by storing the name of the class in a column that by
+ # default is named "type" (can be changed by overwriting <tt>Base.inheritance_column</tt>).
+ # This means that an inheritance looking like this:
#
# class Company < ActiveRecord::Base; end
# class Firm < Company; end
# class Client < Company; end
# class PriorityClient < Client; end
#
- # When you do <tt>Firm.create(:name => "37signals")</tt>, this record will be saved in the companies table with type = "Firm". You can then
- # fetch this row again using <tt>Company.where(:name => '37signals').first</tt> and it will return a Firm object.
+ # When you do <tt>Firm.create(:name => "37signals")</tt>, this record will be saved in
+ # the companies table with type = "Firm". You can then fetch this row again using
+ # <tt>Company.where(:name => '37signals').first</tt> and it will return a Firm object.
#
- # If you don't have a type column defined in your table, single-table inheritance won't be triggered. In that case, it'll work just
- # like normal subclasses with no special magic for differentiating between them or reloading the right type with find.
+ # If you don't have a type column defined in your table, single-table inheritance won't
+ # be triggered. In that case, it'll work just like normal subclasses with no special magic
+ # for differentiating between them or reloading the right type with find.
#
# Note, all the attributes for all the cases are kept in the same table. Read more:
# http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
#
# == Connection to multiple databases in different models
#
- # Connections are usually created through ActiveRecord::Base.establish_connection and retrieved by ActiveRecord::Base.connection.
- # All classes inheriting from ActiveRecord::Base will use this connection. But you can also set a class-specific connection.
- # For example, if Course is an ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
+ # Connections are usually created through ActiveRecord::Base.establish_connection and retrieved
+ # by ActiveRecord::Base.connection. All classes inheriting from ActiveRecord::Base will use this
+ # connection. But you can also set a class-specific connection. For example, if Course is an
+ # ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
# and Course and all of its subclasses will use this connection instead.
#
- # This feature is implemented by keeping a connection pool in ActiveRecord::Base that is a Hash indexed by the class. If a connection is
- # requested, the retrieve_connection method will go up the class-hierarchy until a connection is found in the connection pool.
+ # This feature is implemented by keeping a connection pool in ActiveRecord::Base that is
+ # a Hash indexed by the class. If a connection is requested, the retrieve_connection method
+ # will go up the class-hierarchy until a connection is found in the connection pool.
#
# == Exceptions
#
# * ActiveRecordError - Generic error class and superclass of all other errors raised by Active Record.
# * AdapterNotSpecified - The configuration hash used in <tt>establish_connection</tt> didn't include an
# <tt>:adapter</tt> key.
- # * AdapterNotFound - The <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified a non-existent adapter
+ # * AdapterNotFound - The <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified a
+ # non-existent adapter
# (or a bad spelling of an existing one).
- # * AssociationTypeMismatch - The object assigned to the association wasn't of the type specified in the association definition.
+ # * AssociationTypeMismatch - The object assigned to the association wasn't of the type
+ # specified in the association definition.
# * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
- # * ConnectionNotEstablished+ - No connection has been established. Use <tt>establish_connection</tt> before querying.
+ # * ConnectionNotEstablished+ - No connection has been established. Use <tt>establish_connection</tt>
+ # before querying.
# * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist
# or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal
# nothing was found, please check its documentation for further details.
# * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
# * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
- # <tt>attributes=</tt> method. The +errors+ property of this exception contains an array of AttributeAssignmentError
+ # <tt>attributes=</tt> method. The +errors+ property of this exception contains an array of
+ # AttributeAssignmentError
# objects that should be inspected to determine which attributes triggered the errors.
- # * AttributeAssignmentError - An error occurred while doing a mass assignment through the <tt>attributes=</tt> method.
- # You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error.
+ # * AttributeAssignmentError - An error occurred while doing a mass assignment through the
+ # <tt>attributes=</tt> method.
+ # You can inspect the +attribute+ property of the exception object to determine which attribute
+ # triggered the error.
#
# *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
# So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
@@ -275,8 +311,9 @@ module ActiveRecord #:nodoc:
class Base
##
# :singleton-method:
- # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
- # on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
+ # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class,
+ # which is then passed on to any new database connections made and which can be retrieved on both
+ # a class and instance level by calling +logger+.
cattr_accessor :logger, :instance_writer => false
class << self
@@ -323,21 +360,24 @@ module ActiveRecord #:nodoc:
##
# :singleton-method:
- # Accessor for the prefix type that will be prepended to every primary key column name. The options are :table_name and
- # :table_name_with_underscore. If the first is specified, the Product class will look for "productid" instead of "id" as
- # the primary column. If the latter is specified, the Product class will look for "product_id" instead of "id". Remember
+ # Accessor for the prefix type that will be prepended to every primary key column name.
+ # The options are :table_name and :table_name_with_underscore. If the first is specified,
+ # the Product class will look for "productid" instead of "id" as the primary column. If the
+ # latter is specified, the Product class will look for "product_id" instead of "id". Remember
# that this is a global setting for all Active Records.
cattr_accessor :primary_key_prefix_type, :instance_writer => false
@@primary_key_prefix_type = nil
##
# :singleton-method:
- # Accessor for the name of the prefix string to prepend to every table name. So if set to "basecamp_", all
- # table names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient way of creating a namespace
- # for tables in a shared database. By default, the prefix is the empty string.
+ # Accessor for the name of the prefix string to prepend to every table name. So if set
+ # to "basecamp_", all table names will be named like "basecamp_projects", "basecamp_people",
+ # etc. This is a convenient way of creating a namespace for tables in a shared database.
+ # By default, the prefix is the empty string.
#
- # If you are organising your models within modules you can add a prefix to the models within a namespace by defining
- # a singleton method in the parent module called table_name_prefix which returns your chosen prefix.
+ # If you are organising your models within modules you can add a prefix to the models within
+ # a namespace by defining a singleton method in the parent module called table_name_prefix which
+ # returns your chosen prefix.
class_attribute :table_name_prefix, :instance_writer => false
self.table_name_prefix = ""
@@ -358,8 +398,8 @@ module ActiveRecord #:nodoc:
##
# :singleton-method:
- # Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling dates and times from the database.
- # This is set to :local by default.
+ # Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling
+ # dates and times from the database. This is set to :local by default.
cattr_accessor :default_timezone, :instance_writer => false
@@default_timezone = :local
@@ -398,7 +438,7 @@ module ActiveRecord #:nodoc:
delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped
delegate :find_each, :find_in_batches, :to => :scoped
- delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
+ delegate :select, :group, :order, :reorder, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
# Executes a custom SQL query against your database and returns all the results. The results will
@@ -476,7 +516,8 @@ module ActiveRecord #:nodoc:
connection.select_value(sql, "#{name} Count").to_i
end
- # Attributes listed as readonly can be set for a new record, but will be ignored in database updates afterwards.
+ # Attributes listed as readonly will be used to create a new record but update operations will
+ # ignore these fields.
def attr_readonly(*attributes)
write_inheritable_attribute(:attr_readonly, Set.new(attributes.map(&:to_s)) + (readonly_attributes || []))
end
@@ -505,15 +546,18 @@ module ActiveRecord #:nodoc:
serialized_attributes[attr_name.to_s] = class_name
end
- # Returns a hash of all the attributes that have been specified for serialization as keys and their class restriction as values.
+ # Returns a hash of all the attributes that have been specified for serialization as
+ # keys and their class restriction as values.
def serialized_attributes
read_inheritable_attribute(:attr_serialized) or write_inheritable_attribute(:attr_serialized, {})
end
- # Guesses the table name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending
- # directly from ActiveRecord::Base. So if the hierarchy looks like: Reply < Message < ActiveRecord::Base, then Message is used
- # to guess the table name even when called on Reply. The rules used to do the guess are handled by the Inflector class
- # in Active Support, which knows almost all common English inflections. You can add new inflections in config/initializers/inflections.rb.
+ # Guesses the table name (in forced lower-case) based on the name of the class in the
+ # inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy
+ # looks like: Reply < Message < ActiveRecord::Base, then Message is used
+ # to guess the table name even when called on Reply. The rules used to do the guess
+ # are handled by the Inflector class in Active Support, which knows almost all common
+ # English inflections. You can add new inflections in config/initializers/inflections.rb.
#
# Nested classes are given table names prefixed by the singular form of
# the parent's table name. Enclosing modules are not considered.
@@ -561,8 +605,8 @@ module ActiveRecord #:nodoc:
(parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
end
- # Defines the column name for use with single table inheritance
- # -- can be set in subclasses like so: self.inheritance_column = "type_id"
+ # Defines the column name for use with single table inheritance. Use
+ # <tt>set_inheritance_column</tt> to set a different value.
def inheritance_column
@inheritance_column ||= "type".freeze
end
@@ -579,8 +623,8 @@ module ActiveRecord #:nodoc:
default
end
- # Sets the table name to use to the given value, or (if the value
- # is nil or false) to the value returned by the given block.
+ # Sets the table name. If the value is nil or false then the value returned by the given
+ # block is used.
#
# class Project < ActiveRecord::Base
# set_table_name "project"
@@ -803,7 +847,7 @@ module ActiveRecord #:nodoc:
end
def arel_table
- @arel_table ||= Arel::Table.new(table_name, :engine => arel_engine)
+ @arel_table ||= Arel::Table.new(table_name, arel_engine)
end
def arel_engine
@@ -923,15 +967,15 @@ module ActiveRecord #:nodoc:
end
end
- # Enables dynamic finders like <tt>find_by_user_name(user_name)</tt> and <tt>find_by_user_name_and_password(user_name, password)</tt>
- # that are turned into <tt>where(:user_name => user_name).first</tt> and <tt>where(:user_name => user_name, :password => :password).first</tt>
- # respectively. Also works for <tt>all</tt> by using <tt>find_all_by_amount(50)</tt> that is turned into <tt>where(:amount => 50).all</tt>.
+ # Enables dynamic finders like <tt>User.find_by_user_name(user_name)</tt> and
+ # <tt>User.scoped_by_user_name(user_name). Refer to Dynamic attribute-based finders
+ # section at the top of this file for more detailed information.
#
- # It's even possible to use all the additional parameters to +find+. For example, the full interface for +find_all_by_amount+
- # is actually <tt>find_all_by_amount(amount, options)</tt>.
+ # It's even possible to use all the additional parameters to +find+. For example, the
+ # full interface for +find_all_by_amount+ is actually <tt>find_all_by_amount(amount, options)</tt>.
#
- # Each dynamic finder, scope or initializer/creator is also defined in the class after it is first invoked, so that future
- # attempts to use it do not run through method_missing.
+ # Each dynamic finder using <tt>scoped_by_*</tt> is also defined in the class after it
+ # is first invoked, so that future attempts to use it do not run through method_missing.
def method_missing(method_id, *arguments, &block)
if match = DynamicFinderMatch.match(method_id)
attribute_names = match.attribute_names
@@ -991,8 +1035,8 @@ module ActiveRecord #:nodoc:
end
protected
- # Scope parameters to method calls within the block. Takes a hash of method_name => parameters hash.
- # method_name may be <tt>:find</tt> or <tt>:create</tt>. <tt>:find</tt> parameter is <tt>Relation</tt> while
+ # with_scope lets you apply options to inner block incrementally. It takes a hash and the keys must be
+ # <tt>:find</tt> or <tt>:create</tt>. <tt>:find</tt> parameter is <tt>Relation</tt> while
# <tt>:create</tt> parameters are an attributes hash.
#
# class Article < ActiveRecord::Base
@@ -1030,15 +1074,14 @@ module ActiveRecord #:nodoc:
# class Article < ActiveRecord::Base
# def self.find_with_exclusive_scope
# with_scope(:find => where(:blog_id => 1).limit(1)) do
- # with_exclusive_scope(:find => limit(10))
+ # with_exclusive_scope(:find => limit(10)) do
# all # => SELECT * from articles LIMIT 10
# end
# end
# end
# end
#
- # *Note*: the +:find+ scope also has effect on update and deletion methods,
- # like +update_all+ and +delete_all+.
+ # *Note*: the +:find+ scope also has effect on update and deletion methods, like +update_all+ and +delete_all+.
def with_scope(method_scoping = {}, action = :merge, &block)
method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)
@@ -1255,6 +1298,8 @@ MSG
replace_named_bind_variables(statement, values.first)
elsif statement.include?('?')
replace_bind_variables(statement, values)
+ elsif statement.blank?
+ statement
else
statement % values.collect { |value| connection.quote_string(value.to_s) }
end
@@ -1355,7 +1400,7 @@ MSG
# as it copies the object's attributes only, not its associations. The extent of a "deep" clone is
# application specific and is therefore left to the application to implement according to its need.
def initialize_copy(other)
- callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
+ _run_after_initialize_callbacks if respond_to?(:_run_after_initialize_callbacks)
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
cloned_attributes.delete(self.class.primary_key)
@@ -1471,7 +1516,7 @@ MSG
# user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false)
# user.is_admin? # => true
def attributes=(new_attributes, guard_protected_attributes = true)
- return unless new_attributes.is_a? Hash
+ return unless new_attributes.is_a?(Hash)
attributes = new_attributes.stringify_keys
multi_parameter_attributes = []
@@ -1605,10 +1650,11 @@ MSG
private
- # Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord::Base descendant.
- # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to do Reply.new without having to
- # set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself. No such attribute would be set for objects of the
- # Message class in that example.
+ # Sets the attribute used for single table inheritance to this class name if this is not the
+ # ActiveRecord::Base descendant.
+ # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
+ # do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself.
+ # No such attribute would be set for objects of the Message class in that example.
def ensure_proper_type
unless self.class.descends_from_active_record?
write_attribute(self.class.inheritance_column, self.class.sti_name)
@@ -1657,8 +1703,9 @@ MSG
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
- # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum, f for Float,
- # s for String, and a for Array. If all the values for a given attribute are empty, the attribute will be set to nil.
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum,
+ # f for Float, s for String, and a for Array. If all the values for a given attribute are empty, the
+ # attribute will be set to nil.
def assign_multiparameter_attributes(pairs)
execute_callstack_for_multiparameter_attributes(
extract_callstack_for_multiparameter_attributes(pairs)
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 637dac450b..aa92bf999f 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -26,8 +26,8 @@ module ActiveRecord
# <tt>after_rollback</tt>.
#
# That's a total of ten callbacks, which gives you immense power to react and prepare for each state in the
- # Active Record lifecycle. The sequence for calling <tt>Base#save</tt> for an existing record is similar, except that each
- # <tt>_on_create</tt> callback is replaced by the corresponding <tt>_on_update</tt> callback.
+ # Active Record lifecycle. The sequence for calling <tt>Base#save</tt> for an existing record is similar,
+ # except that each <tt>_on_create</tt> callback is replaced by the corresponding <tt>_on_update</tt> callback.
#
# Examples:
# class CreditCard < ActiveRecord::Base
@@ -55,9 +55,9 @@ module ActiveRecord
#
# == Inheritable callback queues
#
- # Besides the overwritable callback methods, it's also possible to register callbacks through the use of the callback macros.
- # Their main advantage is that the macros add behavior into a callback queue that is kept intact down through an inheritance
- # hierarchy. Example:
+ # Besides the overwritable callback methods, it's also possible to register callbacks through the
+ # use of the callback macros. Their main advantage is that the macros add behavior into a callback
+ # queue that is kept intact down through an inheritance hierarchy.
#
# class Topic < ActiveRecord::Base
# before_destroy :destroy_author
@@ -67,9 +67,9 @@ module ActiveRecord
# before_destroy :destroy_readers
# end
#
- # Now, when <tt>Topic#destroy</tt> is run only +destroy_author+ is called. When <tt>Reply#destroy</tt> is run, both +destroy_author+ and
- # +destroy_readers+ are called. Contrast this to the situation where we've implemented the save behavior through overwriteable
- # methods:
+ # Now, when <tt>Topic#destroy</tt> is run only +destroy_author+ is called. When <tt>Reply#destroy</tt> is
+ # run, both +destroy_author+ and +destroy_readers+ are called. Contrast this to the following situation
+ # where the +before_destroy+ methis is overriden:
#
# class Topic < ActiveRecord::Base
# def before_destroy() destroy_author end
@@ -79,20 +79,21 @@ module ActiveRecord
# def before_destroy() destroy_readers end
# end
#
- # In that case, <tt>Reply#destroy</tt> would only run +destroy_readers+ and _not_ +destroy_author+. So, use the callback macros when
- # you want to ensure that a certain callback is called for the entire hierarchy, and use the regular overwriteable methods
- # when you want to leave it up to each descendant to decide whether they want to call +super+ and trigger the inherited callbacks.
+ # In that case, <tt>Reply#destroy</tt> would only run +destroy_readers+ and _not_ +destroy_author+.
+ # So, use the callback macros when you want to ensure that a certain callback is called for the entire
+ # hierarchy, and use the regular overwriteable methods when you want to leave it up to each descendant
+ # to decide whether they want to call +super+ and trigger the inherited callbacks.
#
- # *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the callbacks before specifying the
- # associations. Otherwise, you might trigger the loading of a child before the parent has registered the callbacks and they won't
- # be inherited.
+ # *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the
+ # callbacks before specifying the associations. Otherwise, you might trigger the loading of a
+ # child before the parent has registered the callbacks and they won't be inherited.
#
# == Types of callbacks
#
# There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects,
- # inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects are the
- # recommended approaches, inline methods using a proc are sometimes appropriate (such as for creating mix-ins), and inline
- # eval methods are deprecated.
+ # inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects
+ # are the recommended approaches, inline methods using a proc are sometimes appropriate (such as for
+ # creating mix-ins), and inline eval methods are deprecated.
#
# The method reference callbacks work by specifying a protected or private method available in the object, like this:
#
@@ -169,15 +170,15 @@ module ActiveRecord
# end
# end
#
- # The callback macros usually accept a symbol for the method they're supposed to run, but you can also pass a "method string",
- # which will then be evaluated within the binding of the callback. Example:
+ # The callback macros usually accept a symbol for the method they're supposed to run, but you can also
+ # pass a "method string", which will then be evaluated within the binding of the callback. Example:
#
# class Topic < ActiveRecord::Base
# before_destroy 'self.class.delete_all "parent_id = #{id}"'
# end
#
- # Notice that single quotes (') are used so the <tt>#{id}</tt> part isn't evaluated until the callback is triggered. Also note that these
- # inline callbacks can be stacked just like the regular ones:
+ # Notice that single quotes (') are used so the <tt>#{id}</tt> part isn't evaluated until the callback
+ # is triggered. Also note that these inline callbacks can be stacked just like the regular ones:
#
# class Topic < ActiveRecord::Base
# before_destroy 'self.class.delete_all "parent_id = #{id}"',
@@ -186,22 +187,24 @@ module ActiveRecord
#
# == The +after_find+ and +after_initialize+ exceptions
#
- # Because +after_find+ and +after_initialize+ are called for each object found and instantiated by a finder, such as <tt>Base.find(:all)</tt>, we've had
- # to implement a simple performance constraint (50% more speed on a simple test case). Unlike all the other callbacks, +after_find+ and
- # +after_initialize+ will only be run if an explicit implementation is defined (<tt>def after_find</tt>). In that case, all of the
+ # Because +after_find+ and +after_initialize+ are called for each object found and instantiated by a finder,
+ # such as <tt>Base.find(:all)</tt>, we've had to implement a simple performance constraint (50% more speed
+ # on a simple test case). Unlike all the other callbacks, +after_find+ and +after_initialize+ will only be
+ # run if an explicit implementation is defined (<tt>def after_find</tt>). In that case, all of the
# callback types will be called.
#
# == <tt>before_validation*</tt> returning statements
#
- # If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be aborted and <tt>Base#save</tt> will return +false+.
- # If Base#save! is called it will raise a ActiveRecord::RecordInvalid exception.
- # Nothing will be appended to the errors object.
+ # If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be
+ # aborted and <tt>Base#save</tt> will return +false+. If Base#save! is called it will raise a
+ # ActiveRecord::RecordInvalid exception. Nothing will be appended to the errors object.
#
# == Canceling callbacks
#
- # If a <tt>before_*</tt> callback returns +false+, all the later callbacks and the associated action are cancelled. If an <tt>after_*</tt> callback returns
- # +false+, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks
- # defined as methods on the model, which are called last.
+ # If a <tt>before_*</tt> callback returns +false+, all the later callbacks and the associated action are
+ # cancelled. If an <tt>after_*</tt> callback returns +false+, all the later callbacks are cancelled.
+ # Callbacks are generally run in the order they are defined, with the exception of callbacks defined as
+ # methods on the model, which are called last.
#
# == Transactions
#
@@ -217,7 +220,8 @@ module ActiveRecord
#
# == Debugging callbacks
#
- # To list the methods and procs registered with a particular callback, append <tt>_callback_chain</tt> to the callback name that you wish to list and send that to your class from the Rails console:
+ # To list the methods and procs registered with a particular callback, append <tt>_callback_chain</tt> to
+ # the callback name that you wish to list and send that to your class from the Rails console:
#
# >> Topic.after_save_callback_chain
# => [#<ActiveSupport::Callbacks::Callback:0x3f6a448
@@ -228,7 +232,7 @@ module ActiveRecord
extend ActiveSupport::Concern
CALLBACKS = [
- :after_initialize, :after_find, :before_validation, :after_validation,
+ :after_initialize, :after_find, :after_touch, :before_validation, :after_validation,
:before_save, :around_save, :after_save, :before_create, :around_create,
:after_create, :before_update, :around_update, :after_update,
:before_destroy, :around_destroy, :after_destroy
@@ -238,7 +242,7 @@ module ActiveRecord
extend ActiveModel::Callbacks
include ActiveModel::Validations::Callbacks
- define_model_callbacks :initialize, :find, :only => :after
+ define_model_callbacks :initialize, :find, :touch, :only => :after
define_model_callbacks :save, :create, :update, :destroy
end
@@ -256,6 +260,10 @@ module ActiveRecord
_run_destroy_callbacks { super }
end
+ def touch(*) #:nodoc:
+ _run_touch_callbacks { super }
+ end
+
def deprecated_callback_method(symbol) #:nodoc:
if respond_to?(symbol, true)
ActiveSupport::Deprecation.warn("Overwriting #{symbol} in your models has been deprecated, please use Base##{symbol} :method_name instead")
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index c2d79a421d..02a8f4e214 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -103,8 +103,8 @@ module ActiveRecord
# Signal that the thread is finished with the current connection.
# #release_connection releases the connection-thread association
# and returns the connection to the pool.
- def release_connection
- conn = @reserved_connections.delete(current_connection_id)
+ def release_connection(with_id = current_connection_id)
+ conn = @reserved_connections.delete(with_id)
checkin conn if conn
end
@@ -112,10 +112,11 @@ module ActiveRecord
# exists checkout a connection, yield it to the block, and checkin the
# connection when finished.
def with_connection
- fresh_connection = true unless @reserved_connections[current_connection_id]
+ connection_id = current_connection_id
+ fresh_connection = true unless @reserved_connections[connection_id]
yield connection
ensure
- release_connection if fresh_connection
+ release_connection(connection_id) if fresh_connection
end
# Returns true if a connection has already been opened.
@@ -161,8 +162,13 @@ module ActiveRecord
# Return any checked-out connections back to the pool by threads that
# are no longer alive.
def clear_stale_cached_connections!
- remove_stale_cached_threads!(@reserved_connections) do |name, conn|
- checkin conn
+ keys = @reserved_connections.keys - Thread.list.find_all { |t|
+ t.alive?
+ }.map { |thread| thread.object_id }
+
+ keys.each do |key|
+ checkin @reserved_connections[key]
+ @reserved_connections.delete(key)
end
end
@@ -232,20 +238,6 @@ module ActiveRecord
Thread.current.object_id
end
- # Remove stale threads from the cache.
- def remove_stale_cached_threads!(cache, &block)
- keys = Set.new(cache.keys)
-
- Thread.list.each do |thread|
- keys.delete(thread.object_id) if thread.alive?
- end
- keys.each do |key|
- next unless cache.has_key?(key)
- block.call(key, cache[key])
- cache.delete(key)
- end
- end
-
def checkout_new_connection
c = new_connection
@connections << c
@@ -290,14 +282,12 @@ module ActiveRecord
# ActiveRecord::Base.connection_handler. Active Record models use this to
# determine that connection pool that they should use.
class ConnectionHandler
+ attr_reader :connection_pools
+
def initialize(pools = {})
@connection_pools = pools
end
- def connection_pools
- @connection_pools ||= {}
- end
-
def establish_connection(name, spec)
@connection_pools[name] = ConnectionAdapters::ConnectionPool.new(spec)
end
@@ -345,9 +335,11 @@ module ActiveRecord
# re-establishing the connection.
def remove_connection(klass)
pool = @connection_pools[klass.name]
+ return nil unless pool
+
@connection_pools.delete_if { |key, value| value == pool }
- pool.disconnect! if pool
- pool.spec.config if pool
+ pool.disconnect!
+ pool.spec.config
end
def retrieve_connection_pool(klass)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
index 23c42d670b..8e74eff0ab 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
@@ -66,15 +66,9 @@ module ActiveRecord
unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end
begin
- require 'rubygems'
- gem "activerecord-#{spec[:adapter]}-adapter"
require "active_record/connection_adapters/#{spec[:adapter]}_adapter"
rescue LoadError
- begin
- require "active_record/connection_adapters/#{spec[:adapter]}_adapter"
- rescue LoadError
- raise "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{$!})"
- end
+ raise "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{$!})"
end
adapter_method = "#{spec[:adapter]}_connection"
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
index 4118ea7b31..a130c330dd 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
@@ -42,7 +42,7 @@ module ActiveRecord
65535
end
- # the maximum length of a SQL query
+ # the maximum length of an SQL query
def sql_query_length
1048575
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index d7b5bf8e31..e2b3773a99 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -30,7 +30,7 @@ module ActiveRecord
if value.acts_like?(:date) || value.acts_like?(:time)
"'#{quoted_date(value)}'"
else
- "'#{quote_string(value.to_yaml)}'"
+ "'#{quote_string(value.to_s)}'"
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 7691b6a788..9118ceb33c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -23,7 +23,8 @@ module ActiveRecord
#
# +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id int(11)</tt>.
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
- # +sql_type+ is used to extract the column's length, if necessary. For example +60+ in <tt>company_name varchar(60)</tt>.
+ # +sql_type+ is used to extract the column's length, if necessary. For example +60+ in
+ # <tt>company_name varchar(60)</tt>.
# It will be mapped to one of the standard Rails SQL types in the <tt>type</tt> attribute.
# +null+ determines if this column allows +NULL+ values.
def initialize(name, default, sql_type = nil, null = true)
@@ -359,7 +360,8 @@ module ActiveRecord
#
# Available options are (none of these exists by default):
# * <tt>:limit</tt> -
- # Requests a maximum column length. This is number of characters for <tt>:string</tt> and <tt>:text</tt> columns and number of bytes for :binary and :integer columns.
+ # Requests a maximum column length. This is number of characters for <tt>:string</tt> and
+ # <tt>:text</tt> columns and number of bytes for :binary and :integer columns.
# * <tt>:default</tt> -
# The column's default value. Use nil for NULL.
# * <tt>:null</tt> -
@@ -462,8 +464,8 @@ module ActiveRecord
# TableDefinition#timestamps that'll add created_at and +updated_at+ as datetimes.
#
# TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type
- # column if the <tt>:polymorphic</tt> option is supplied. If <tt>:polymorphic</tt> is a hash of options, these will be
- # used when creating the <tt>_type</tt> column. So what can be written like this:
+ # column if the <tt>:polymorphic</tt> option is supplied. If <tt>:polymorphic</tt> is a hash of
+ # options, these will be used when creating the <tt>_type</tt> column. So what can be written like this:
#
# create_table :taggings do |t|
# t.integer :tag_id, :tagger_id, :taggable_id
@@ -535,7 +537,7 @@ module ActiveRecord
end
end
- # Represents a SQL table in an abstract way for updating a table.
+ # Represents an SQL table in an abstract way for updating a table.
# Also see TableDefinition and SchemaStatements#create_table
#
# Available transformations are:
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index ffc3847a31..7dee68502f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -327,6 +327,8 @@ module ActiveRecord
#
# Note: SQLite doesn't support index length
def add_index(table_name, column_name, options = {})
+ options[:name] = options[:name].to_s if options.key?(:name)
+
column_names = Array.wrap(column_name)
index_name = index_name(table_name, :column => column_names)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index be8d1bd76b..d8c92d0ad3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -36,14 +36,12 @@ module ActiveRecord
define_callbacks :checkout, :checkin
- @@row_even = true
-
def initialize(connection, logger = nil) #:nodoc:
@active = nil
@connection, @logger = connection, logger
- @runtime = 0
@query_cache_enabled = false
@query_cache = {}
+ @instrumenter = ActiveSupport::Notifications.instrumenter
end
# Returns the human-readable name of the adapter. Use mixed case - one
@@ -92,11 +90,6 @@ module ActiveRecord
false
end
- def reset_runtime #:nodoc:
- rt, @runtime = @runtime, 0
- rt
- end
-
# QUOTING ==================================================
# Override to return the quoted table name. Defaults to column quoting.
@@ -199,12 +192,10 @@ module ActiveRecord
def log(sql, name)
name ||= "SQL"
- result = nil
- ActiveSupport::Notifications.instrument("sql.active_record",
- :sql => sql, :name => name, :connection_id => self.object_id) do
- @runtime += Benchmark.ms { result = yield }
+ @instrumenter.instrument("sql.active_record",
+ :sql => sql, :name => name, :connection_id => object_id) do
+ yield
end
- result
rescue Exception => e
message = "#{e.class.name}: #{e.message}: #{sql}"
@logger.debug message if @logger
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
new file mode 100644
index 0000000000..568759775b
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -0,0 +1,639 @@
+# encoding: utf-8
+
+require 'mysql2' unless defined? Mysql2
+
+module ActiveRecord
+ class Base
+ def self.mysql2_connection(config)
+ config[:username] = 'root' if config[:username].nil?
+ client = Mysql2::Client.new(config.symbolize_keys)
+ options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
+ ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
+ end
+ end
+
+ module ConnectionAdapters
+ class Mysql2Column < Column
+ BOOL = "tinyint(1)"
+ def extract_default(default)
+ if sql_type =~ /blob/i || type == :text
+ if default.blank?
+ return null ? nil : ''
+ else
+ raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
+ end
+ elsif missing_default_forged_as_empty_string?(default)
+ nil
+ else
+ super
+ end
+ end
+
+ def has_default?
+ return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
+ super
+ end
+
+ # Returns the Ruby class that corresponds to the abstract data type.
+ def klass
+ case type
+ when :integer then Fixnum
+ when :float then Float
+ when :decimal then BigDecimal
+ when :datetime then Time
+ when :date then Date
+ when :timestamp then Time
+ when :time then Time
+ when :text, :string then String
+ when :binary then String
+ when :boolean then Object
+ end
+ end
+
+ def type_cast(value)
+ return nil if value.nil?
+ case type
+ when :string then value
+ when :text then value
+ when :integer then value.to_i rescue value ? 1 : 0
+ when :float then value.to_f # returns self if it's already a Float
+ when :decimal then self.class.value_to_decimal(value)
+ when :datetime, :timestamp then value.class == Time ? value : self.class.string_to_time(value)
+ when :time then value.class == Time ? value : self.class.string_to_dummy_time(value)
+ when :date then value.class == Date ? value : self.class.string_to_date(value)
+ when :binary then value
+ when :boolean then self.class.value_to_boolean(value)
+ else value
+ end
+ end
+
+ def type_cast_code(var_name)
+ case type
+ when :string then nil
+ when :text then nil
+ when :integer then "#{var_name}.to_i rescue #{var_name} ? 1 : 0"
+ when :float then "#{var_name}.to_f"
+ when :decimal then "#{self.class.name}.value_to_decimal(#{var_name})"
+ when :datetime, :timestamp then "#{var_name}.class == Time ? #{var_name} : #{self.class.name}.string_to_time(#{var_name})"
+ when :time then "#{var_name}.class == Time ? #{var_name} : #{self.class.name}.string_to_dummy_time(#{var_name})"
+ when :date then "#{var_name}.class == Date ? #{var_name} : #{self.class.name}.string_to_date(#{var_name})"
+ when :binary then nil
+ when :boolean then "#{self.class.name}.value_to_boolean(#{var_name})"
+ else nil
+ end
+ end
+
+ private
+ def simplified_type(field_type)
+ return :boolean if Mysql2Adapter.emulate_booleans && field_type.downcase.index(BOOL)
+ return :string if field_type =~ /enum/i or field_type =~ /set/i
+ return :integer if field_type =~ /year/i
+ return :binary if field_type =~ /bit/i
+ super
+ end
+
+ def extract_limit(sql_type)
+ case sql_type
+ when /blob|text/i
+ case sql_type
+ when /tiny/i
+ 255
+ when /medium/i
+ 16777215
+ when /long/i
+ 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
+ else
+ super # we could return 65535 here, but we leave it undecorated by default
+ end
+ when /^bigint/i; 8
+ when /^int/i; 4
+ when /^mediumint/i; 3
+ when /^smallint/i; 2
+ when /^tinyint/i; 1
+ else
+ super
+ end
+ end
+
+ # MySQL misreports NOT NULL column default when none is given.
+ # We can't detect this for columns which may have a legitimate ''
+ # default (string) but we can for others (integer, datetime, boolean,
+ # and the rest).
+ #
+ # Test whether the column has default '', is not null, and is not
+ # a type allowing default ''.
+ def missing_default_forged_as_empty_string?(default)
+ type != :string && !null && default == ''
+ end
+ end
+
+ class Mysql2Adapter < AbstractAdapter
+ cattr_accessor :emulate_booleans
+ self.emulate_booleans = true
+
+ ADAPTER_NAME = 'Mysql2'
+ PRIMARY = "PRIMARY"
+
+ LOST_CONNECTION_ERROR_MESSAGES = [
+ "Server shutdown in progress",
+ "Broken pipe",
+ "Lost connection to MySQL server during query",
+ "MySQL server has gone away" ]
+
+ QUOTED_TRUE, QUOTED_FALSE = '1', '0'
+
+ NATIVE_DATABASE_TYPES = {
+ :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
+ :string => { :name => "varchar", :limit => 255 },
+ :text => { :name => "text" },
+ :integer => { :name => "int", :limit => 4 },
+ :float => { :name => "float" },
+ :decimal => { :name => "decimal" },
+ :datetime => { :name => "datetime" },
+ :timestamp => { :name => "datetime" },
+ :time => { :name => "time" },
+ :date => { :name => "date" },
+ :binary => { :name => "blob" },
+ :boolean => { :name => "tinyint", :limit => 1 }
+ }
+
+ def initialize(connection, logger, connection_options, config)
+ super(connection, logger)
+ @connection_options, @config = connection_options, config
+ @quoted_column_names, @quoted_table_names = {}, {}
+ configure_connection
+ end
+
+ def adapter_name
+ ADAPTER_NAME
+ end
+
+ def supports_migrations?
+ true
+ end
+
+ def supports_primary_key?
+ true
+ end
+
+ def supports_savepoints?
+ true
+ end
+
+ def native_database_types
+ NATIVE_DATABASE_TYPES
+ end
+
+ # QUOTING ==================================================
+
+ def quote(value, column = nil)
+ if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
+ s = column.class.string_to_binary(value).unpack("H*")[0]
+ "x'#{s}'"
+ elsif value.kind_of?(BigDecimal)
+ value.to_s("F")
+ else
+ super
+ end
+ end
+
+ def quote_column_name(name) #:nodoc:
+ @quoted_column_names[name] ||= "`#{name}`"
+ end
+
+ def quote_table_name(name) #:nodoc:
+ @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
+ end
+
+ def quote_string(string)
+ @connection.escape(string)
+ end
+
+ def quoted_true
+ QUOTED_TRUE
+ end
+
+ def quoted_false
+ QUOTED_FALSE
+ end
+
+ # REFERENTIAL INTEGRITY ====================================
+
+ def disable_referential_integrity(&block) #:nodoc:
+ old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
+
+ begin
+ update("SET FOREIGN_KEY_CHECKS = 0")
+ yield
+ ensure
+ update("SET FOREIGN_KEY_CHECKS = #{old}")
+ end
+ end
+
+ # CONNECTION MANAGEMENT ====================================
+
+ def active?
+ return false unless @connection
+ @connection.query 'select 1'
+ true
+ rescue Mysql2::Error
+ false
+ end
+
+ def reconnect!
+ disconnect!
+ connect
+ end
+
+ # this is set to true in 2.3, but we don't want it to be
+ def requires_reloading?
+ false
+ end
+
+ def disconnect!
+ unless @connection.nil?
+ @connection.close
+ @connection = nil
+ end
+ end
+
+ def reset!
+ disconnect!
+ connect
+ end
+
+ # DATABASE STATEMENTS ======================================
+
+ # FIXME: re-enable the following once a "better" query_cache solution is in core
+ #
+ # The overrides below perform much better than the originals in AbstractAdapter
+ # because we're able to take advantage of mysql2's lazy-loading capabilities
+ #
+ # # Returns a record hash with the column names as keys and column values
+ # # as values.
+ # def select_one(sql, name = nil)
+ # result = execute(sql, name)
+ # result.each(:as => :hash) do |r|
+ # return r
+ # end
+ # end
+ #
+ # # Returns a single value from a record
+ # def select_value(sql, name = nil)
+ # result = execute(sql, name)
+ # if first = result.first
+ # first.first
+ # end
+ # end
+ #
+ # # Returns an array of the values of the first column in a select:
+ # # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
+ # def select_values(sql, name = nil)
+ # execute(sql, name).map { |row| row.first }
+ # end
+
+ # Returns an array of arrays containing the field values.
+ # Order is the same as that returned by +columns+.
+ def select_rows(sql, name = nil)
+ execute(sql, name).to_a
+ end
+
+ # Executes the SQL statement in the context of this connection.
+ def execute(sql, name = nil)
+ # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
+ # made since we established the connection
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
+ if name == :skip_logging
+ @connection.query(sql)
+ else
+ log(sql, name) { @connection.query(sql) }
+ end
+ rescue ActiveRecord::StatementInvalid => exception
+ if exception.message.split(":").first =~ /Packets out of order/
+ raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
+ else
+ raise
+ end
+ end
+
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
+ super
+ id_value || @connection.last_id
+ end
+ alias :create :insert_sql
+
+ def update_sql(sql, name = nil)
+ super
+ @connection.affected_rows
+ end
+
+ def begin_db_transaction
+ execute "BEGIN"
+ rescue Exception
+ # Transactions aren't supported
+ end
+
+ def commit_db_transaction
+ execute "COMMIT"
+ rescue Exception
+ # Transactions aren't supported
+ end
+
+ def rollback_db_transaction
+ execute "ROLLBACK"
+ rescue Exception
+ # Transactions aren't supported
+ end
+
+ def create_savepoint
+ execute("SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def rollback_to_savepoint
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def release_savepoint
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def add_limit_offset!(sql, options)
+ limit, offset = options[:limit], options[:offset]
+ if limit && offset
+ sql << " LIMIT #{offset.to_i}, #{sanitize_limit(limit)}"
+ elsif limit
+ sql << " LIMIT #{sanitize_limit(limit)}"
+ elsif offset
+ sql << " OFFSET #{offset.to_i}"
+ end
+ sql
+ end
+
+ # SCHEMA STATEMENTS ========================================
+
+ def structure_dump
+ if supports_views?
+ sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
+ else
+ sql = "SHOW TABLES"
+ end
+
+ select_all(sql).inject("") do |structure, table|
+ table.delete('Table_type')
+ structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n"
+ end
+ end
+
+ def recreate_database(name, options = {})
+ drop_database(name)
+ create_database(name, options)
+ end
+
+ # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
+ # Charset defaults to utf8.
+ #
+ # Example:
+ # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
+ # create_database 'matt_development'
+ # create_database 'matt_development', :charset => :big5
+ def create_database(name, options = {})
+ if options[:collation]
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
+ else
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
+ end
+ end
+
+ def drop_database(name) #:nodoc:
+ execute "DROP DATABASE IF EXISTS `#{name}`"
+ end
+
+ def current_database
+ select_value 'SELECT DATABASE() as db'
+ end
+
+ # Returns the database character set.
+ def charset
+ show_variable 'character_set_database'
+ end
+
+ # Returns the database collation strategy.
+ def collation
+ show_variable 'collation_database'
+ end
+
+ def tables(name = nil)
+ tables = []
+ execute("SHOW TABLES", name).each do |field|
+ tables << field.first
+ end
+ tables
+ end
+
+ def drop_table(table_name, options = {})
+ super(table_name, options)
+ end
+
+ def indexes(table_name, name = nil)
+ indexes = []
+ current_index = nil
+ result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
+ result.each(:symbolize_keys => true, :as => :hash) do |row|
+ if current_index != row[:Key_name]
+ next if row[:Key_name] == PRIMARY # skip the primary key
+ current_index = row[:Key_name]
+ indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique] == 0, [])
+ end
+
+ indexes.last.columns << row[:Column_name]
+ end
+ indexes
+ end
+
+ def columns(table_name, name = nil)
+ sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
+ columns = []
+ result = execute(sql, :skip_logging)
+ result.each(:symbolize_keys => true, :as => :hash) { |field|
+ columns << Mysql2Column.new(field[:Field], field[:Default], field[:Type], field[:Null] == "YES")
+ }
+ columns
+ end
+
+ def create_table(table_name, options = {})
+ super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
+ end
+
+ def rename_table(table_name, new_name)
+ execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
+ end
+
+ def add_column(table_name, column_name, type, options = {})
+ add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
+ add_column_options!(add_column_sql, options)
+ add_column_position!(add_column_sql, options)
+ execute(add_column_sql)
+ end
+
+ def change_column_default(table_name, column_name, default)
+ column = column_for(table_name, column_name)
+ change_column table_name, column_name, column.sql_type, :default => default
+ end
+
+ def change_column_null(table_name, column_name, null, default = nil)
+ column = column_for(table_name, column_name)
+
+ unless null || default.nil?
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
+ end
+
+ change_column table_name, column_name, column.sql_type, :null => null
+ end
+
+ def change_column(table_name, column_name, type, options = {})
+ column = column_for(table_name, column_name)
+
+ unless options_include_default?(options)
+ options[:default] = column.default
+ end
+
+ unless options.has_key?(:null)
+ options[:null] = column.null
+ end
+
+ change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
+ add_column_options!(change_column_sql, options)
+ add_column_position!(change_column_sql, options)
+ execute(change_column_sql)
+ end
+
+ def rename_column(table_name, column_name, new_column_name)
+ options = {}
+ if column = columns(table_name).find { |c| c.name == column_name.to_s }
+ options[:default] = column.default
+ options[:null] = column.null
+ else
+ raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
+ end
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
+ rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
+ add_column_options!(rename_column_sql, options)
+ execute(rename_column_sql)
+ end
+
+ # Maps logical Rails types to MySQL-specific data types.
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
+ return super unless type.to_s == 'integer'
+
+ case limit
+ when 1; 'tinyint'
+ when 2; 'smallint'
+ when 3; 'mediumint'
+ when nil, 4, 11; 'int(11)' # compatibility with MySQL default
+ when 5..8; 'bigint'
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
+ end
+ end
+
+ def add_column_position!(sql, options)
+ if options[:first]
+ sql << " FIRST"
+ elsif options[:after]
+ sql << " AFTER #{quote_column_name(options[:after])}"
+ end
+ end
+
+ def show_variable(name)
+ variables = select_all("SHOW VARIABLES LIKE '#{name}'")
+ variables.first['Value'] unless variables.empty?
+ end
+
+ def pk_and_sequence_for(table)
+ keys = []
+ result = execute("describe #{quote_table_name(table)}")
+ result.each(:symbolize_keys => true, :as => :hash) do |row|
+ keys << row[:Field] if row[:Key] == "PRI"
+ end
+ keys.length == 1 ? [keys.first, nil] : nil
+ end
+
+ # Returns just a table's primary key
+ def primary_key(table)
+ pk_and_sequence = pk_and_sequence_for(table)
+ pk_and_sequence && pk_and_sequence.first
+ end
+
+ def case_sensitive_equality_operator
+ "= BINARY"
+ end
+
+ def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
+ where_sql
+ end
+
+ protected
+ def quoted_columns_for_index(column_names, options = {})
+ length = options[:length] if options.is_a?(Hash)
+
+ quoted_column_names = case length
+ when Hash
+ column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
+ when Fixnum
+ column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
+ else
+ column_names.map {|name| quote_column_name(name) }
+ end
+ end
+
+ def translate_exception(exception, message)
+ return super unless exception.respond_to?(:error_number)
+
+ case exception.error_number
+ when 1062
+ RecordNotUnique.new(message, exception)
+ when 1452
+ InvalidForeignKey.new(message, exception)
+ else
+ super
+ end
+ end
+
+ private
+ def connect
+ @connection = Mysql2::Client.new(@config)
+ configure_connection
+ end
+
+ def configure_connection
+ @connection.query_options.merge!(:as => :array)
+ encoding = @config[:encoding]
+ execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
+
+ # By default, MySQL 'where id is null' selects the last inserted id.
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
+ execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
+ end
+
+ # Returns an array of record hashes with the column names as keys and
+ # column values as values.
+ def select(sql, name = nil)
+ execute(sql, name).each(:as => :hash)
+ end
+
+ def supports_views?
+ version[0] >= 5
+ end
+
+ def version
+ @version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
+ end
+
+ def column_for(table_name, column_name)
+ unless column = columns(table_name).find { |c| c.name == column_name.to_s }
+ raise "No such column: #{table_name}.#{column_name}"
+ end
+ column
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index aa3626a37e..ba0051de05 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -125,7 +125,7 @@ module ActiveRecord
# By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
# as boolean. If you wish to disable this emulation (which was the default
# behavior in versions 0.13.1 and earlier) you can add the following line
- # to your environment.rb file:
+ # to your application.rb file:
#
# ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
cattr_accessor :emulate_booleans
@@ -278,7 +278,8 @@ module ActiveRecord
rows
end
- # Executes a SQL query and returns a MySQL::Result object. Note that you have to free the Result object after you're done using it.
+ # Executes an SQL query and returns a MySQL::Result object. Note that you have to free
+ # the Result object after you're done using it.
def execute(sql, name = nil) #:nodoc:
if name == :skip_logging
@connection.query(sql)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 2fe2ae7136..6fae899e87 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -183,10 +183,14 @@ module ActiveRecord
# * <tt>:username</tt> - Defaults to nothing.
# * <tt>:password</tt> - Defaults to nothing.
# * <tt>:database</tt> - The name of the database. No default, must be provided.
- # * <tt>:schema_search_path</tt> - An optional schema search path for the connection given as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
- # * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO <encoding></tt> call on the connection.
- # * <tt>:min_messages</tt> - An optional client min messages that is used in a <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
- # * <tt>:allow_concurrency</tt> - If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods.
+ # * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
+ # as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
+ # * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
+ # <encoding></tt> call on the connection.
+ # * <tt>:min_messages</tt> - An optional client min messages that is used in a
+ # <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
+ # * <tt>:allow_concurrency</tt> - If true, use async query methods so Ruby threads don't deadlock;
+ # otherwise, use blocking query methods.
class PostgreSQLAdapter < AbstractAdapter
ADAPTER_NAME = 'PostgreSQL'.freeze
@@ -218,6 +222,9 @@ module ActiveRecord
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
@local_tz = nil
+ @table_alias_length = nil
+ @postgresql_version = nil
+
connect
@local_tz = execute('SHOW TIME ZONE').first["TimeZone"]
end
@@ -308,14 +315,16 @@ module ActiveRecord
# Quotes PostgreSQL-specific data types for SQL input.
def quote(value, column = nil) #:nodoc:
- if value.kind_of?(String) && column && column.type == :binary
+ return super unless column
+
+ if value.kind_of?(String) && column.type == :binary
"'#{escape_bytea(value)}'"
- elsif value.kind_of?(String) && column && column.sql_type == 'xml'
+ elsif value.kind_of?(String) && column.sql_type == 'xml'
"xml '#{quote_string(value)}'"
- elsif value.kind_of?(Numeric) && column && column.sql_type == 'money'
+ elsif value.kind_of?(Numeric) && column.sql_type == 'money'
# Not truly string input, so doesn't require (or allow) escape string syntax.
- "'#{value.to_s}'"
- elsif value.kind_of?(String) && column && column.sql_type =~ /^bit/
+ "'#{value}'"
+ elsif value.kind_of?(String) && column.sql_type =~ /^bit/
case value
when /^[01]*$/
"B'#{value}'" # Bit-string notation
@@ -370,7 +379,7 @@ module ActiveRecord
def supports_disable_referential_integrity?() #:nodoc:
version = query("SHOW server_version")[0][0].split('.')
- (version[0].to_i >= 8 && version[1].to_i >= 1) ? true : false
+ version[0].to_i >= 8 && version[1].to_i >= 1
rescue
return false
end
@@ -431,17 +440,37 @@ module ActiveRecord
def result_as_array(res) #:nodoc:
# check if we have any binary column and if they need escaping
unescape_col = []
- for j in 0...res.nfields do
- # unescape string passed BYTEA field (OID == 17)
- unescape_col << ( res.ftype(j)==17 )
+ res.nfields.times do |j|
+ unescape_col << res.ftype(j)
end
ary = []
- for i in 0...res.ntuples do
+ res.ntuples.times do |i|
ary << []
- for j in 0...res.nfields do
+ res.nfields.times do |j|
data = res.getvalue(i,j)
- data = unescape_bytea(data) if unescape_col[j] and data.is_a?(String)
+ case unescape_col[j]
+
+ # unescape string passed BYTEA field (OID == 17)
+ when BYTEA_COLUMN_TYPE_OID
+ data = unescape_bytea(data) if String === data
+
+ # If this is a money type column and there are any currency symbols,
+ # then strip them off. Indeed it would be prettier to do this in
+ # PostgreSQLColumn.string_to_decimal but would break form input
+ # fields that call value_before_type_cast.
+ when MONEY_COLUMN_TYPE_OID
+ # Because money output is formatted according to the locale, there are two
+ # cases to consider (note the decimal separators):
+ # (1) $12,345,678.12
+ # (2) $12.345.678,12
+ case data
+ when /^-?\D+[\d,]+\.\d{2}$/ # (1)
+ data.gsub!(/[^-\d\.]/, '')
+ when /^-?\D+[\d\.]+,\d{2}$/ # (2)
+ data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
+ end
+ end
ary[i] << data
end
end
@@ -828,11 +857,12 @@ module ActiveRecord
# Maps logical Rails types to PostgreSQL-specific data types.
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
return super unless type.to_s == 'integer'
+ return 'integer' unless limit
case limit
- when 1..2; 'smallint'
- when 3..4, nil; 'integer'
- when 5..8; 'bigint'
+ when 1, 2; 'smallint'
+ when 3, 4; 'integer'
+ when 5..8; 'bigint'
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
end
end
@@ -889,6 +919,8 @@ module ActiveRecord
private
# The internal PostgreSQL identifier of the money data type.
MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
+ # The internal PostgreSQL identifier of the BYTEA data type.
+ BYTEA_COLUMN_TYPE_OID = 17 #:nodoc:
# Connects to a PostgreSQL server and sets up the adapter depending on the
# connected server's characteristics.
@@ -941,51 +973,17 @@ module ActiveRecord
# conversions that are required to be performed here instead of in PostgreSQLColumn.
def select(sql, name = nil)
fields, rows = select_raw(sql, name)
- result = []
- for row in rows
- row_hash = {}
- fields.each_with_index do |f, i|
- row_hash[f] = row[i]
- end
- result << row_hash
+ rows.map do |row|
+ Hash[*fields.zip(row).flatten]
end
- result
end
def select_raw(sql, name = nil)
res = execute(sql, name)
results = result_as_array(res)
- fields = []
- rows = []
- if res.ntuples > 0
- fields = res.fields
- results.each do |row|
- hashed_row = {}
- row.each_index do |cell_index|
- # If this is a money type column and there are any currency symbols,
- # then strip them off. Indeed it would be prettier to do this in
- # PostgreSQLColumn.string_to_decimal but would break form input
- # fields that call value_before_type_cast.
- if res.ftype(cell_index) == MONEY_COLUMN_TYPE_OID
- # Because money output is formatted according to the locale, there are two
- # cases to consider (note the decimal separators):
- # (1) $12,345,678.12
- # (2) $12.345.678,12
- case column = row[cell_index]
- when /^-?\D+[\d,]+\.\d{2}$/ # (1)
- row[cell_index] = column.gsub(/[^-\d\.]/, '')
- when /^-?\D+[\d\.]+,\d{2}$/ # (2)
- row[cell_index] = column.gsub(/[^-\d,]/, '').sub(/,/, '.')
- end
- end
-
- hashed_row[fields[cell_index]] = column
- end
- rows << row
- end
- end
+ fields = res.fields
res.clear
- return fields, rows
+ return fields, results
end
# Returns the list of a table's column names, data types, and default values.
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 117cf447df..82ad0a3b8e 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -29,8 +29,8 @@ module ActiveRecord
end
end
- # The SQLite adapter works with both the 2.x and 3.x series of SQLite with the sqlite-ruby drivers (available both as gems and
- # from http://rubyforge.org/projects/sqlite-ruby/).
+ # The SQLite adapter works with both the 2.x and 3.x series of SQLite with the sqlite-ruby
+ # drivers (available both as gems and from http://rubyforge.org/projects/sqlite-ruby/).
#
# Options:
#
@@ -190,16 +190,21 @@ module ActiveRecord
def indexes(table_name, name = nil) #:nodoc:
execute("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row|
- index = IndexDefinition.new(table_name, row['name'])
- index.unique = row['unique'].to_i != 0
- index.columns = execute("PRAGMA index_info('#{index.name}')").map { |col| col['name'] }
- index
+ IndexDefinition.new(
+ table_name,
+ row['name'],
+ row['unique'].to_i != 0,
+ execute("PRAGMA index_info('#{row['name']}')").map { |col|
+ col['name']
+ })
end
end
def primary_key(table_name) #:nodoc:
- column = table_structure(table_name).find {|field| field['pk'].to_i == 1}
- column ? column['name'] : nil
+ column = table_structure(table_name).find { |field|
+ field['pk'].to_i == 1
+ }
+ column && column['name']
end
def remove_index!(table_name, index_name) #:nodoc:
@@ -278,10 +283,8 @@ module ActiveRecord
def select(sql, name = nil) #:nodoc:
execute(sql, name).map do |row|
record = {}
- row.each_key do |key|
- if key.is_a?(String)
- record[key.sub(/^"?\w+"?\./, '')] = row[key]
- end
+ row.each do |key, value|
+ record[key.sub(/^"?\w+"?\./, '')] = value if key.is_a?(String)
end
record
end
@@ -378,9 +381,9 @@ module ActiveRecord
def default_primary_key_type
if supports_autoincrement?
- 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'.freeze
+ 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'
else
- 'INTEGER PRIMARY KEY NOT NULL'.freeze
+ 'INTEGER PRIMARY KEY NOT NULL'
end
end
diff --git a/activerecord/lib/active_record/dynamic_finder_match.rb b/activerecord/lib/active_record/dynamic_finder_match.rb
index b39b291352..0dc965bd26 100644
--- a/activerecord/lib/active_record/dynamic_finder_match.rb
+++ b/activerecord/lib/active_record/dynamic_finder_match.rb
@@ -2,8 +2,8 @@ module ActiveRecord
# = Active Record Dynamic Finder Match
#
- # Provides dynamic attribute-based finders such as <tt>find_by_country</tt>
- # if, for example, the <tt>Person</tt> has an attribute with that name.
+ # Refer to ActiveRecord::Base documentation for Dynamic attribute-based finders for detailed info
+ #
class DynamicFinderMatch
def self.match(method)
df_match = self.new(method)
@@ -42,6 +42,10 @@ module ActiveRecord
@finder == :first && !@instantiator.nil?
end
+ def creator?
+ @finder == :first && @instantiator == :create
+ end
+
def bang?
@bang
end
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 7aa725d095..e9ac5516ec 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -30,7 +30,8 @@ module ActiveRecord
class SerializationTypeMismatch < ActiveRecordError
end
- # Raised when adapter not specified on connection (or configuration file <tt>config/database.yml</tt> misses adapter field).
+ # Raised when adapter not specified on connection (or configuration file <tt>config/database.yml</tt>
+ # misses adapter field).
class AdapterNotSpecified < ActiveRecordError
end
@@ -38,7 +39,8 @@ module ActiveRecord
class AdapterNotFound < ActiveRecordError
end
- # Raised when connection to the database could not been established (for example when <tt>connection=</tt> is given a nil object).
+ # Raised when connection to the database could not been established (for example when <tt>connection=</tt>
+ # is given a nil object).
class ConnectionNotEstablished < ActiveRecordError
end
@@ -51,7 +53,8 @@ module ActiveRecord
class RecordNotSaved < ActiveRecordError
end
- # Raised when SQL statement cannot be executed by the database (for example, it's often the case for MySQL when Ruby driver used is too old).
+ # Raised when SQL statement cannot be executed by the database (for example, it's often the case for
+ # MySQL when Ruby driver used is too old).
class StatementInvalid < ActiveRecordError
end
@@ -78,7 +81,8 @@ module ActiveRecord
class InvalidForeignKey < WrappedDatabaseException
end
- # Raised when number of bind variables in statement given to <tt>:condition</tt> key (for example, when using +find+ method)
+ # Raised when number of bind variables in statement given to <tt>:condition</tt> key (for example,
+ # when using +find+ method)
# does not match number of expected variables.
#
# For example, in
@@ -165,4 +169,4 @@ module ActiveRecord
@errors = errors
end
end
-end \ No newline at end of file
+end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 82270c56b3..e44102b538 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -39,9 +39,10 @@ end
# This type of fixture is in YAML format and the preferred default. YAML is a file format which describes data structures
# in a non-verbose, human-readable format. It ships with Ruby 1.8.1+.
#
-# Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which are placed in 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/</tt>). The fixture file ends with the <tt>.yml</tt> file extension (Rails example:
+# Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which are placed
+# in 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/</tt>).
+# The fixture file ends with the <tt>.yml</tt> file extension (Rails example:
# <tt><your-rails-app>/test/fixtures/web_sites.yml</tt>). The format of a YAML fixture file looks like this:
#
# rubyonrails:
@@ -58,7 +59,8 @@ end
# indented list of key/value pairs in the "key: value" format. Records are separated by a blank line for your viewing
# pleasure.
#
-# Note that YAML fixtures are unordered. If you want ordered fixtures, use the omap YAML type. See http://yaml.org/type/omap.html
+# Note that YAML fixtures are unordered. If you want ordered fixtures, use the omap YAML type.
+# See http://yaml.org/type/omap.html
# for the specification. You will need ordered fixtures when you have foreign key constraints on keys in the same table.
# This is commonly needed for tree structures. Example:
#
@@ -79,7 +81,8 @@ end
# (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
+# 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
@@ -99,15 +102,16 @@ end
#
# == 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> --
+# 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:
+# 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
@@ -133,7 +137,8 @@ end
# end
# end
#
-# By default, the <tt>test_helper module</tt> will load all of your fixtures into your test database, so this test will succeed.
+# By default, the <tt>test_helper module</tt> will load all of your fixtures into your test database,
+# so this test will succeed.
# The testing environment will automatically load the all fixtures into the database before each test.
# To ensure consistent data, the environment deletes the fixtures before running the load.
#
@@ -182,13 +187,15 @@ end
# This will create 1000 very simple YAML fixtures.
#
# Using ERb, you can also inject dynamic values into your fixtures with inserts like <tt><%= Date.today.strftime("%Y-%m-%d") %></tt>.
-# This is however a feature to be used with some caution. The point of fixtures are that they're stable units of predictable
-# sample data. If you feel that you need to inject dynamic values, then perhaps you should reexamine whether your application
-# is properly testable. Hence, dynamic values in fixtures are to be considered a code smell.
+# This is however a feature to be used with some caution. The point of fixtures are that they're
+# stable units of predictable sample data. If you feel that you need to inject dynamic values, then
+# perhaps you should reexamine whether your application is properly testable. Hence, dynamic values
+# in fixtures are to be considered a code smell.
#
# = Transactional fixtures
#
-# TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case.
+# TestCases can use begin+rollback to isolate their changes to the database instead of having to
+# delete+insert for every test case.
#
# class FooTest < ActiveSupport::TestCase
# self.use_transactional_fixtures = true
@@ -205,15 +212,18 @@ end
# end
#
# If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures,
-# then you may omit all fixtures declarations in your test cases since all the data's already there and every case rolls back its changes.
+# then you may omit all fixtures declarations in your test cases since all the data's already there
+# and every case rolls back its changes.
#
# In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide
-# access to fixture data for every table that has been loaded through fixtures (depending on the value of +use_instantiated_fixtures+)
+# access to fixture data for every table that has been loaded through fixtures (depending on the
+# value of +use_instantiated_fixtures+)
#
# When *not* to use transactional fixtures:
#
-# 1. You're testing whether a transaction works correctly. Nested transactions don't commit until all parent transactions commit,
-# particularly, the fixtures transaction which is begun in setup and rolled back in teardown. Thus, you won't be able to verify
+# 1. You're testing whether a transaction works correctly. Nested transactions don't commit until
+# all parent transactions commit, particularly, the fixtures transaction which is begun in setup
+# and rolled back in teardown. Thus, you won't be able to verify
# the results of your transaction until Active Record supports nested transactions or savepoints (in progress).
# 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
# Use InnoDB, MaxDB, or NDB instead.
@@ -664,14 +674,13 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
end
def has_primary_key_column?
- @has_primary_key_column ||= model_class && primary_key_name &&
- model_class.columns.find { |c| c.name == primary_key_name }
+ @has_primary_key_column ||= primary_key_name &&
+ model_class.columns.any? { |c| c.name == primary_key_name }
end
def timestamp_column_names
- @timestamp_column_names ||= %w(created_at created_on updated_at updated_on).select do |name|
- column_names.include?(name)
- end
+ @timestamp_column_names ||=
+ %w(created_at created_on updated_at updated_on) & column_names
end
def inheritance_column_name
@@ -872,7 +881,7 @@ module ActiveRecord
table_names.each do |table_name|
table_name = table_name.to_s.tr('./', '_')
- define_method(table_name) do |*fixtures|
+ redefine_method(table_name) do |*fixtures|
force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
@fixture_cache[table_name] ||= {}
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index ceb0902fde..b6f87a57b8 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -124,6 +124,7 @@ module ActiveRecord
end
end
+ @destroyed = true
freeze
end
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index 71065f9908..c7ae12977a 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -1,19 +1,35 @@
module ActiveRecord
class LogSubscriber < ActiveSupport::LogSubscriber
+ def self.runtime=(value)
+ Thread.current["active_record_sql_runtime"] = value
+ end
+
+ def self.runtime
+ Thread.current["active_record_sql_runtime"] ||= 0
+ end
+
+ def self.reset_runtime
+ rt, self.runtime = runtime, 0
+ rt
+ end
+
def initialize
super
@odd_or_even = false
end
def sql(event)
+ self.class.runtime += event.duration
+ return unless logger.debug?
+
name = '%s (%.1fms)' % [event.payload[:name], event.duration]
sql = event.payload[:sql].squeeze(' ')
if odd?
- name = color(name, :cyan, true)
+ name = color(name, CYAN, true)
sql = color(sql, nil, true)
else
- name = color(name, :magenta, true)
+ name = color(name, MAGENTA, true)
end
debug " #{name} #{sql}"
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 4c5e1ae218..5e272f0ba4 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -284,7 +284,7 @@ module ActiveRecord
#
# config.active_record.timestamped_migrations = false
#
- # In environment.rb.
+ # In application.rb.
#
class Migration
@@verbose = true
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index 849ec9c884..0e560418dc 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -26,7 +26,7 @@ module ActiveRecord
# You can define a \scope that applies to all finders using
# ActiveRecord::Base.default_scope.
def scoped(options = nil)
- if options.present?
+ if options
scoped.apply_finder_options(options)
else
current_scoped_methods ? relation.merge(current_scoped_methods) : relation.clone
@@ -48,18 +48,21 @@ module ActiveRecord
# The above calls to <tt>scope</tt> define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red,
# in effect, represents the query <tt>Shirt.where(:color => 'red')</tt>.
#
- # Unlike <tt>Shirt.find(...)</tt>, however, the object returned by Shirt.red is not an Array; it resembles the association object
- # constructed by a <tt>has_many</tt> declaration. For instance, you can invoke <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>,
- # <tt>Shirt.red.where(:size => 'small')</tt>. Also, just as with the association objects, named \scopes act like an Array,
- # implementing Enumerable; <tt>Shirt.red.each(&block)</tt>, <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt>
+ # Unlike <tt>Shirt.find(...)</tt>, however, the object returned by Shirt.red is not an Array; it
+ # resembles the association object constructed by a <tt>has_many</tt> declaration. For instance,
+ # you can invoke <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>, <tt>Shirt.red.where(:size => 'small')</tt>.
+ # Also, just as with the association objects, named \scopes act like an Array, implementing Enumerable;
+ # <tt>Shirt.red.each(&block)</tt>, <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt>
# all behave as if Shirt.red really was an Array.
#
- # These named \scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are both red and dry clean only.
- # Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt> returns the number of garments
- # for which these criteria obtain. Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
+ # These named \scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce
+ # all shirts that are both red and dry clean only.
+ # Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt>
+ # returns the number of garments for which these criteria obtain. Similarly with
+ # <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
#
- # All \scopes are available as class methods on the ActiveRecord::Base descendant upon which the \scopes were defined. But they are also available to
- # <tt>has_many</tt> associations. If,
+ # All \scopes are available as class methods on the ActiveRecord::Base descendant upon which
+ # the \scopes were defined. But they are also available to <tt>has_many</tt> associations. If,
#
# class Person < ActiveRecord::Base
# has_many :shirts
@@ -105,7 +108,7 @@ module ActiveRecord
extension ? relation.extending(extension) : relation
end
- singleton_class.send :define_method, name, &scopes[name]
+ singleton_class.send(:redefine_method, name, &scopes[name])
end
def named_scope(*args, &block)
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index cf8c5aaf84..e652296e2c 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -78,7 +78,7 @@ module ActiveRecord
# member.avatar_attributes = { :id => '2', :_destroy => '1' }
# member.avatar.marked_for_destruction? # => true
# member.save
- # member.reload.avatar #=> nil
+ # member.reload.avatar # => nil
#
# Note that the model will _not_ be destroyed until the parent is saved.
#
@@ -180,7 +180,7 @@ module ActiveRecord
#
# member.attributes = params['member']
# member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true
- # member.posts.length #=> 2
+ # member.posts.length # => 2
# member.save
# member.reload.posts.length # => 1
#
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
index d2ed643f35..78bac55bf2 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -67,8 +67,8 @@ module ActiveRecord
#
# == Configuration
#
- # In order to activate an observer, list it in the <tt>config.active_record.observers</tt> configuration setting in your
- # <tt>config/environment.rb</tt> file.
+ # In order to activate an observer, list it in the <tt>config.active_record.observers</tt> configuration
+ # setting in your <tt>config/application.rb</tt> file.
#
# config.active_record.observers = :comment_observer, :signup_observer
#
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 828a8b41b6..71b46beaef 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -60,7 +60,7 @@ module ActiveRecord
# reflect that no changes should be made (since they can't be
# persisted). Returns the frozen instance.
#
- # The row is simply removed with a SQL +DELETE+ statement on the
+ # The row is simply removed with an SQL +DELETE+ statement on the
# record's primary key, and no callbacks are executed.
#
# To enforce the object's +before_destroy+ and +after_destroy+
@@ -91,8 +91,8 @@ module ActiveRecord
# like render <tt>:partial => @client.becomes(Company)</tt> to render that
# instance using the companies/company partial instead of clients/client.
#
- # Note: The new instance will share a link to the same attributes as the original class. So any change to the attributes in either
- # instance will affect the other.
+ # Note: The new instance will share a link to the same attributes as the original class.
+ # So any change to the attributes in either instance will affect the other.
def becomes(klass)
became = klass.new
became.instance_variable_set("@attributes", @attributes)
@@ -102,35 +102,57 @@ module ActiveRecord
became
end
- # Updates a single attribute and saves the record without going through the normal validation procedure
- # or callbacks. This is especially useful for boolean flags on existing records.
+ # Updates a single attribute and saves the record.
+ # This is especially useful for boolean flags on existing records. Also note that
+ #
+ # * The attribute being updated must be a column name.
+ # * Validation is skipped.
+ # * No callbacks are invoked.
+ # * updated_at/updated_on column is updated if that column is available.
+ # * Does not work on associations.
+ # * Does not work on attr_accessor attributes.
+ # * Does not work on new record. <tt>record.new_record?</tt> should return false for this method to work.
+ # * Updates only the attribute that is input to the method. If there are other changed attributes then
+ # those attributes are left alone. In that case even after this method has done its work <tt>record.changed?</tt>
+ # will return true.
+ #
def update_attribute(name, value)
- send("#{name}=", value)
- hash = { name => read_attribute(name) }
+ raise ActiveRecordError, "#{name.to_s} is marked as readonly" if self.class.readonly_attributes.include? name.to_s
+
+ changes = record_update_timestamps || {}
- if record_update_timestamps
- timestamp_attributes_for_update_in_model.each do |column|
- hash[column] = read_attribute(column)
- end
+ if name
+ name = name.to_s
+ send("#{name}=", value)
+ changes[name] = read_attribute(name)
end
- @changed_attributes.delete(name.to_s)
+ @changed_attributes.except!(*changes.keys)
primary_key = self.class.primary_key
- self.class.update_all(hash, { primary_key => self[primary_key] }) == 1
+ self.class.update_all(changes, { primary_key => self[primary_key] }) == 1
end
- # Updates all the attributes from the passed-in Hash and saves the record.
- # If the object is invalid, the saving will fail and false will be returned.
+ # Updates the attributes of the model from the passed-in hash and saves the
+ # record, all wrapped in a transaction. If the object is invalid, the saving
+ # will fail and false will be returned.
def update_attributes(attributes)
- self.attributes = attributes
- save
+ # The following transaction covers any possible database side-effects of the
+ # attributes assignment. For example, setting the IDs of a child collection.
+ with_transaction_returning_status do
+ self.attributes = attributes
+ save
+ end
end
- # Updates an object just like Base.update_attributes but calls save! instead
- # of save so an exception is raised if the record is invalid.
+ # Updates its receiver just like +update_attributes+ but calls <tt>save!</tt> instead
+ # of +save+, so an exception is raised if the record is invalid.
def update_attributes!(attributes)
- self.attributes = attributes
- save!
+ # The following transaction covers any possible database side-effects of the
+ # attributes assignment. For example, setting the IDs of a child collection.
+ with_transaction_returning_status do
+ self.attributes = attributes
+ save!
+ end
end
# Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).
@@ -196,6 +218,19 @@ module ActiveRecord
self
end
+ # Saves the record with the updated_at/on attributes set to the current time.
+ # Please note that no validation is performed and no callbacks are executed.
+ # If an attribute name is passed, that attribute is updated along with
+ # updated_at/on attributes.
+ #
+ # Examples:
+ #
+ # product.touch # updates updated_at/on
+ # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on
+ def touch(attribute = nil)
+ update_attribute(attribute, current_time_from_proper_timezone)
+ end
+
private
def create_or_update
raise ReadOnlyRecord if readonly?
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 2808e199fe..78fdb77216 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -16,16 +16,18 @@ module ActiveRecord
config.generators.orm :active_record, :migration => true,
:timestamps => true
- config.app_middleware.insert_after "::ActionDispatch::Callbacks",
- "ActiveRecord::QueryCache"
-
- config.app_middleware.insert_after "::ActionDispatch::Callbacks",
- "ActiveRecord::ConnectionAdapters::ConnectionManagement"
+ config.app_middleware.insert_after "::ActionDispatch::Callbacks", "ActiveRecord::QueryCache"
rake_tasks do
load "active_record/railties/databases.rake"
end
+ # When loading console, force ActiveRecord to be loaded to avoid cross
+ # references when loading a constant for the first time.
+ console do
+ ActiveRecord::Base
+ end
+
initializer "active_record.initialize_timezone" do
ActiveSupport.on_load(:active_record) do
self.time_zone_aware_attributes = true
@@ -72,6 +74,13 @@ module ActiveRecord
end
end
+ initializer "active_record.add_concurrency_middleware" do |app|
+ if app.config.allow_concurrency
+ app.config.middleware.insert_after "::ActionDispatch::Callbacks",
+ "ActiveRecord::ConnectionAdapters::ConnectionManagement"
+ end
+ end
+
config.after_initialize do
ActiveSupport.on_load(:active_record) do
instantiate_observers
diff --git a/activerecord/lib/active_record/railties/controller_runtime.rb b/activerecord/lib/active_record/railties/controller_runtime.rb
index aed1c59b00..bc6ca936c0 100644
--- a/activerecord/lib/active_record/railties/controller_runtime.rb
+++ b/activerecord/lib/active_record/railties/controller_runtime.rb
@@ -11,9 +11,9 @@ module ActiveRecord
def cleanup_view_runtime
if ActiveRecord::Base.connected?
- db_rt_before_render = ActiveRecord::Base.connection.reset_runtime
+ db_rt_before_render = ActiveRecord::LogSubscriber.reset_runtime
runtime = super
- db_rt_after_render = ActiveRecord::Base.connection.reset_runtime
+ db_rt_after_render = ActiveRecord::LogSubscriber.reset_runtime
self.db_runtime = db_rt_before_render + db_rt_after_render
runtime - db_rt_after_render
else
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 5024787c3c..ae605d3e7a 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -274,7 +274,7 @@ namespace :db do
task :setup => [ 'db:create', 'db:schema:load', 'db:seed' ]
desc 'Load the seed data from db/seeds.rb'
- task :seed => :environment do
+ task :seed => 'db:abort_if_pending_migrations' do
seed_file = File.join(Rails.root, 'db', 'seeds.rb')
load(seed_file) if File.exist?(seed_file)
end
@@ -339,7 +339,7 @@ namespace :db do
end
namespace :structure do
- desc "Dump the database structure to a SQL file"
+ desc "Dump the database structure to an SQL file"
task :dump => :environment do
abcs = ActiveRecord::Base.configurations
case abcs[Rails.env]["adapter"]
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index a82e5d7ed1..7f47a812eb 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -3,14 +3,14 @@ module ActiveRecord
module Reflection # :nodoc:
extend ActiveSupport::Concern
- # Reflection allows you to interrogate Active Record classes and objects
+ # Reflection enables to interrogate Active Record classes and objects
# about their associations and aggregations. This information can,
- # for example, be used in a form builder that took an Active Record object
- # and created input fields for all of the attributes depending on their type
- # and displayed the associations to other objects.
+ # for example, be used in a form builder that takes an Active Record object
+ # and creates input fields for all of the attributes depending on their type
+ # and displays the associations to other objects.
#
- # You can find the interface for the AggregateReflection and AssociationReflection
- # classes in the abstract MacroReflection class.
+ # MacroReflection class has info for AggregateReflection and AssociationReflection
+ # classes.
module ClassMethods
def create_reflection(macro, name, options, active_record)
case macro
@@ -24,7 +24,7 @@ module ActiveRecord
reflection
end
- # Returns a hash containing all AssociationReflection objects for the current class
+ # Returns a hash containing all AssociationReflection objects for the current class.
# Example:
#
# Invoice.reflections
@@ -39,9 +39,9 @@ module ActiveRecord
reflections.values.select { |reflection| reflection.is_a?(AggregateReflection) }
end
- # Returns the AggregateReflection object for the named +aggregation+ (use the symbol). Example:
+ # Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
#
- # Account.reflect_on_aggregation(:balance) # returns the balance AggregateReflection
+ # Account.reflect_on_aggregation(:balance) #=> the balance AggregateReflection
#
def reflect_on_aggregation(aggregation)
reflections[aggregation].is_a?(AggregateReflection) ? reflections[aggregation] : nil
@@ -50,7 +50,7 @@ module ActiveRecord
# Returns an array of AssociationReflection objects for all the
# associations in the class. If you only want to reflect on a certain
# association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
- # <tt>:belongs_to</tt>) for that as the first parameter.
+ # <tt>:belongs_to</tt>) as the first parameter.
#
# Example:
#
@@ -62,9 +62,9 @@ module ActiveRecord
macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
end
- # Returns the AssociationReflection object for the named +association+ (use the symbol). Example:
+ # Returns the AssociationReflection object for the +association+ (use the symbol).
#
- # Account.reflect_on_association(:owner) # returns the owner AssociationReflection
+ # Account.reflect_on_association(:owner) # returns the owner AssociationReflection
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
#
def reflect_on_association(association)
@@ -78,8 +78,7 @@ module ActiveRecord
end
- # Abstract base class for AggregateReflection and AssociationReflection that
- # describes the interface available for both of those classes. Objects of
+ # Abstract base class for AggregateReflection and AssociationReflection. Objects of
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
class MacroReflection
attr_reader :active_record
@@ -88,34 +87,36 @@ module ActiveRecord
@macro, @name, @options, @active_record = macro, name, options, active_record
end
- # Returns the name of the macro. For example, <tt>composed_of :balance,
- # :class_name => 'Money'</tt> will return <tt>:balance</tt> or for
- # <tt>has_many :clients</tt> it will return <tt>:clients</tt>.
- def name
- @name
- end
+ # Returns the name of the macro.
+ #
+ # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:balance</tt>
+ # <tt>has_many :clients</tt> returns <tt>:clients</tt>
+ attr_reader :name
- # Returns the macro type. For example,
- # <tt>composed_of :balance, :class_name => 'Money'</tt> will return <tt>:composed_of</tt>
- # or for <tt>has_many :clients</tt> will return <tt>:has_many</tt>.
- def macro
- @macro
- end
+ # Returns the macro type.
+ #
+ # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:composed_of</tt>
+ # <tt>has_many :clients</tt> returns <tt>:has_many</tt>
+ attr_reader :macro
- # Returns the hash of options used for the macro. For example, it would return <tt>{ :class_name => "Money" }</tt> for
- # <tt>composed_of :balance, :class_name => 'Money'</tt> or +{}+ for <tt>has_many :clients</tt>.
- def options
- @options
- end
+ # Returns the hash of options used for the macro.
+ #
+ # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>{ :class_name => "Money" }</tt>
+ # <tt>has_many :clients</tt> returns +{}+
+ attr_reader :options
- # Returns the class for the macro. For example, <tt>composed_of :balance, :class_name => 'Money'</tt> returns the Money
- # class and <tt>has_many :clients</tt> returns the Client class.
+ # Returns the class for the macro.
+ #
+ # <tt>composed_of :balance, :class_name => 'Money'</tt> returns the Money class
+ # <tt>has_many :clients</tt> returns the Client class
def klass
@klass ||= class_name.constantize
end
- # Returns the class name for the macro. For example, <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>'Money'</tt>
- # and <tt>has_many :clients</tt> returns <tt>'Client'</tt>.
+ # Returns the class name for the macro.
+ #
+ # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>'Money'</tt>
+ # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
def class_name
@class_name ||= options[:class_name] || derive_class_name
end
@@ -130,11 +131,6 @@ module ActiveRecord
@sanitized_conditions ||= klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
end
- # Returns +true+ if +self+ is a +belongs_to+ reflection.
- def belongs_to?
- macro == :belongs_to
- end
-
private
def derive_class_name
name.to_s.camelize
@@ -150,7 +146,7 @@ module ActiveRecord
# Holds all the meta-data about an association as it was specified in the
# Active Record class.
class AssociationReflection < MacroReflection #:nodoc:
- # Returns the target association's class:
+ # Returns the target association's class.
#
# class Author < ActiveRecord::Base
# has_many :books
@@ -159,7 +155,7 @@ module ActiveRecord
# Author.reflect_on_association(:books).klass
# # => Book
#
- # <b>Note:</b> do not call +klass.new+ or +klass.create+ to instantiate
+ # <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate
# a new association object. Use +build_association+ or +create_association+
# instead. This allows plugins to hook into association object creation.
def klass
@@ -206,6 +202,10 @@ module ActiveRecord
@primary_key_name ||= options[:foreign_key] || derive_primary_key_name
end
+ def primary_key_column
+ @primary_key_column ||= klass.columns.find { |c| c.name == klass.primary_key }
+ end
+
def association_foreign_key
@association_foreign_key ||= @options[:association_foreign_key] || class_name.foreign_key
end
@@ -270,7 +270,7 @@ module ActiveRecord
end
# Returns whether or not this association reflection is for a collection
- # association. Returns +true+ if the +macro+ is one of +has_many+ or
+ # association. Returns +true+ if the +macro+ is either +has_many+ or
# +has_and_belongs_to_many+, +false+ otherwise.
def collection?
@collection
@@ -280,7 +280,7 @@ module ActiveRecord
# the parent's validation.
#
# Unless you explicitly disable validation with
- # <tt>:validate => false</tt>, it will take place when:
+ # <tt>:validate => false</tt>, validation will take place when:
#
# * you explicitly enable validation; <tt>:validate => true</tt>
# * you use autosave; <tt>:autosave => true</tt>
@@ -300,6 +300,11 @@ module ActiveRecord
dependent_conditions
end
+ # Returns +true+ if +self+ is a +belongs_to+ reflection.
+ def belongs_to?
+ macro == :belongs_to
+ end
+
private
def derive_class_name
class_name = name.to_s.camelize
@@ -324,8 +329,6 @@ module ActiveRecord
# Gets the source of the through reflection. It checks both a singularized
# and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
#
- # (The <tt>:tags</tt> association on Tagging below.)
- #
# class Post < ActiveRecord::Base
# has_many :taggings
# has_many :tags, :through => :taggings
@@ -336,7 +339,7 @@ module ActiveRecord
end
# Returns the AssociationReflection object specified in the <tt>:through</tt> option
- # of a HasManyThrough or HasOneThrough association. Example:
+ # of a HasManyThrough or HasOneThrough association.
#
# class Post < ActiveRecord::Base
# has_many :taggings
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index d9fc1b4940..30be723291 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -10,17 +10,18 @@ module ActiveRecord
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches
- delegate :to_xml, :to_json, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
+ delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
delegate :insert, :to => :arel
- attr_reader :table, :klass
+ attr_reader :table, :klass, :loaded
attr_accessor :extensions
+ alias :loaded? :loaded
def initialize(klass, table)
@klass, @table = klass, table
@implicit_readonly = nil
- @loaded = nil
+ @loaded = false
SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)}
(ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])}
@@ -66,7 +67,8 @@ module ActiveRecord
preload += @includes_values unless eager_loading?
preload.each {|associations| @klass.send(:preload_associations, @records, associations) }
- # @readonly_value is true only if set explicitly. @implicit_readonly is true if there are JOINS and no explicit SELECT.
+ # @readonly_value is true only if set explicitly. @implicit_readonly is true if there
+ # are JOINS and no explicit SELECT.
readonly = @readonly_value.nil? ? @implicit_readonly : @readonly_value
@records.each { |record| record.readonly! } if readonly
@@ -74,6 +76,8 @@ module ActiveRecord
@records
end
+ def as_json(options = nil) to_a end #:nodoc:
+
# Returns size of the records.
def size
loaded? ? @records.length : count
@@ -96,7 +100,7 @@ module ActiveRecord
if block_given?
to_a.many? { |*block_args| yield(*block_args) }
else
- @limit_value.present? ? to_a.many? : size > 1
+ @limit_value ? to_a.many? : size > 1
end
end
@@ -105,7 +109,7 @@ module ActiveRecord
# ==== Example
#
# Comment.where(:post_id => 1).scoping do
- # Comment.first #=> SELECT * FROM comments WHERE post_id = 1
+ # Comment.first # SELECT * FROM comments WHERE post_id = 1
# end
#
# Please check unscoped if you want to remove all previous scopes (including
@@ -127,7 +131,8 @@ module ActiveRecord
# ==== Parameters
#
# * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
- # * +conditions+ - A string, array, or hash representing the WHERE part of an SQL statement. See conditions in the intro.
+ # * +conditions+ - A string, array, or hash representing the WHERE part of an SQL statement.
+ # See conditions in the intro.
# * +options+ - Additional options are <tt>:limit</tt> and <tt>:order</tt>, see the examples for usage.
#
# ==== Examples
@@ -141,7 +146,7 @@ module ActiveRecord
# # Update all avatars migrated more than a week ago
# Avatar.update_all ['migrated_at = ?', Time.now.utc], ['migrated_at > ?', 1.week.ago]
#
- # # Update all books that match our conditions, but limit it to 5 ordered by date
+ # # Update all books that match conditions, but limit it to 5 ordered by date
# Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5
def update_all(updates, conditions = nil, options = {})
if conditions || options.present?
@@ -162,14 +167,14 @@ module ActiveRecord
# ==== Parameters
#
# * +id+ - This should be the id or an array of ids to be updated.
- # * +attributes+ - This should be a hash of attributes to be set on the object, or an array of hashes.
+ # * +attributes+ - This should be a hash of attributes or an array of hashes.
#
# ==== Examples
#
- # # Updating one record:
+ # # Updates one record
# Person.update(15, :user_name => 'Samuel', :group => 'expert')
#
- # # Updating multiple records:
+ # # Updates multiple records
# people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
# Person.update(people.keys, people.values)
def update(id, attributes)
@@ -290,10 +295,6 @@ module ActiveRecord
where(@klass.primary_key => id_or_array).delete_all
end
- def loaded?
- @loaded
- end
-
def reload
reset
to_a # force reload
@@ -317,12 +318,14 @@ module ActiveRecord
def scope_for_create
@scope_for_create ||= begin
- @create_with_value || @where_values.inject({}) do |hash, where|
- if where.is_a?(Arel::Predicates::Equality)
- hash[where.operand1.name] = where.operand2.respond_to?(:value) ? where.operand2.value : where.operand2
- end
- hash
- end
+ @create_with_value || Hash[
+ @where_values.find_all { |w|
+ w.respond_to?(:operator) && w.operator == :==
+ }.map { |where|
+ [where.operand1.name,
+ where.operand2.respond_to?(:value) ?
+ where.operand2.value : where.operand2]
+ }]
end
end
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 412be895c4..d7494ebb5a 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -50,9 +50,9 @@ module ActiveRecord
def find_in_batches(options = {})
relation = self
- if orders.present? || taken.present?
- ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
- end
+ if orders.present? || taken.present?
+ ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
+ end
if (finder_options = options.except(:start, :batch_size)).present?
raise "You can't specify an order, it's forced to be #{batch_order}" if options[:order].present?
@@ -73,7 +73,7 @@ module ActiveRecord
break if records.size < batch_size
if primary_key_offset = records.last.id
- records = relation.where(primary_key.gt(primary_key_offset)).all
+ records = relation.where(primary_key.gt(primary_key_offset)).to_a
else
raise "Primary key not included in the custom select clause"
end
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 44baeb6c84..a679c444cf 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -1,30 +1,38 @@
require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/object/try'
module ActiveRecord
module Calculations
# Count operates using three different approaches.
#
# * Count all: By not passing any parameters to count, it will return a count of all the rows for the model.
- # * Count using column: By passing a column name to count, it will return a count of all the rows for the model with supplied column present
+ # * Count using column: By passing a column name to count, it will return a count of all the
+ # rows for the model with supplied column present
# * Count using options will find the row count matched by the options used.
#
# The third approach, count using options, accepts an option hash as the only parameter. The options are:
#
- # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro to ActiveRecord::Base.
+ # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
+ # See conditions in the intro to ActiveRecord::Base.
# * <tt>:joins</tt>: Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed)
- # or named associations in the same form used for the <tt>:include</tt> option, which will perform an INNER JOIN on the associated table(s).
- # If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns.
+ # or named associations in the same form used for the <tt>:include</tt> option, which will
+ # perform an INNER JOIN on the associated table(s).
+ # If the value is a string, then the records will be returned read-only since they will have
+ # attributes that do not correspond to the table's columns.
# Pass <tt>:readonly => false</tt> to override.
- # * <tt>:include</tt>: Named associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer
- # to already defined associations. When using named associations, count returns the number of DISTINCT items for the model you're counting.
+ # * <tt>:include</tt>: Named associations that should be loaded alongside using LEFT OUTER JOINs.
+ # The symbols named refer to already defined associations. When using named associations, count
+ # returns the number of DISTINCT items for the model you're counting.
# See eager loading under Associations.
# * <tt>:order</tt>: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
# * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
- # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you, for example, want to do a join but not
+ # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you, for example,
+ # want to do a join but not
# include the joined columns.
- # * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
- # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an alternate table name (or even the name
- # of a database view).
+ # * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as
+ # SELECT COUNT(DISTINCT posts.id) ...
+ # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an
+ # alternate table name (or even the name of a database view).
#
# Examples for counting all:
# Person.count # returns the total count of all people
@@ -34,12 +42,19 @@ module ActiveRecord
#
# Examples for count with options:
# Person.count(:conditions => "age > 26")
- # Person.count(:conditions => "age > 26 AND job.salary > 60000", :include => :job) # because of the named association, it finds the DISTINCT count using LEFT OUTER JOIN.
- # Person.count(:conditions => "age > 26 AND job.salary > 60000", :joins => "LEFT JOIN jobs on jobs.person_id = person.id") # finds the number of rows matching the conditions and joins.
+ #
+ # # because of the named association, it finds the DISTINCT count using LEFT OUTER JOIN.
+ # Person.count(:conditions => "age > 26 AND job.salary > 60000", :include => :job)
+ #
+ # # finds the number of rows matching the conditions and joins.
+ # Person.count(:conditions => "age > 26 AND job.salary > 60000",
+ # :joins => "LEFT JOIN jobs on jobs.person_id = person.id")
+ #
# Person.count('id', :conditions => "age > 26") # Performs a COUNT(id)
# Person.count(:all, :conditions => "age > 26") # Performs a COUNT(*) (:all is an alias for '*')
#
- # Note: <tt>Person.count(:all)</tt> will not work because it will use <tt>:all</tt> as the condition. Use Person.count instead.
+ # Note: <tt>Person.count(:all)</tt> will not work because it will use <tt>:all</tt> as the condition.
+ # Use Person.count instead.
def count(column_name = nil, options = {})
column_name, options = nil, column_name if column_name.is_a?(Hash)
calculate(:count, column_name, options)
@@ -80,13 +95,15 @@ module ActiveRecord
calculate(:sum, column_name, options)
end
- # This calculates aggregate values in the given column. Methods for count, sum, average, minimum, and maximum have been added as shortcuts.
- # Options such as <tt>:conditions</tt>, <tt>:order</tt>, <tt>:group</tt>, <tt>:having</tt>, and <tt>:joins</tt> can be passed to customize the query.
+ # This calculates aggregate values in the given column. Methods for count, sum, average,
+ # minimum, and maximum have been added as shortcuts. Options such as <tt>:conditions</tt>,
+ # <tt>:order</tt>, <tt>:group</tt>, <tt>:having</tt>, and <tt>:joins</tt> can be passed to customize the query.
#
# There are two basic forms of output:
- # * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float for AVG, and the given column's type for everything else.
- # * Grouped values: This returns an ordered hash of the values and groups them by the <tt>:group</tt> option. It takes either a column name, or the name
- # of a belongs_to association.
+ # * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float
+ # for AVG, and the given column's type for everything else.
+ # * Grouped values: This returns an ordered hash of the values and groups them by the
+ # <tt>:group</tt> option. It takes either a column name, or the name of a belongs_to association.
#
# values = Person.maximum(:age, :group => 'last_name')
# puts values["Drake"]
@@ -102,21 +119,30 @@ module ActiveRecord
# end
#
# Options:
- # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro to ActiveRecord::Base.
- # * <tt>:include</tt>: Eager loading, see Associations for details. Since calculations don't load anything, the purpose of this is to access fields on joined tables in your conditions, order, or group clauses.
- # * <tt>:joins</tt> - An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
- # The records will be returned read-only since they will have attributes that do not correspond to the table's columns.
+ # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
+ # See conditions in the intro to ActiveRecord::Base.
+ # * <tt>:include</tt>: Eager loading, see Associations for details. Since calculations don't load anything,
+ # the purpose of this is to access fields on joined tables in your conditions, order, or group clauses.
+ # * <tt>:joins</tt> - An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id".
+ # (Rarely needed).
+ # The records will be returned read-only since they will have attributes that do not correspond to the
+ # table's columns.
# * <tt>:order</tt> - An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
# * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
- # * <tt>:select</tt> - By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
- # include the joined columns.
- # * <tt>:distinct</tt> - Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
+ # * <tt>:select</tt> - By default, this is * as in SELECT * FROM, but can be changed if you for example
+ # want to do a join, but not include the joined columns.
+ # * <tt>:distinct</tt> - Set this to true to make this a distinct calculation, such as
+ # SELECT COUNT(DISTINCT posts.id) ...
#
# Examples:
# Person.calculate(:count, :all) # The same as Person.count
# Person.average(:age) # SELECT AVG(age) FROM people...
- # Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for everyone with a last name other than 'Drake'
- # Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) # Selects the minimum age for any family without any minors
+ # Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for
+ # # everyone with a last name other than 'Drake'
+ #
+ # # Selects the minimum age for any family without any minors
+ # Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name)
+ #
# Person.sum("2 * age")
def calculate(operation, column_name, options = {})
if options.except(:distinct).present?
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 3bf4c5bdd1..b34c11973b 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -21,23 +21,28 @@ module ActiveRecord
#
# ==== Parameters
#
- # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>[ "user_name = ?", username ]</tt>, or <tt>["user_name = :user_name", { :user_name => user_name }]</tt>. See conditions in the intro.
+ # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>[ "user_name = ?", username ]</tt>,
+ # or <tt>["user_name = :user_name", { :user_name => user_name }]</tt>. See conditions in the intro.
# * <tt>:order</tt> - An SQL fragment like "created_at DESC, name".
# * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
- # * <tt>:having</tt> - Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
+ # * <tt>:having</tt> - Combined with +:group+ this can be used to filter the records that a
+ # <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
# * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
- # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4.
+ # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5,
+ # it would skip rows 0 through 4.
# * <tt>:joins</tt> - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed),
- # named associations in the same form used for the <tt>:include</tt> option, which will perform an <tt>INNER JOIN</tt> on the associated table(s),
+ # named associations in the same form used for the <tt>:include</tt> option, which will perform an
+ # <tt>INNER JOIN</tt> on the associated table(s),
# or an array containing a mixture of both strings and named associations.
- # If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns.
+ # If the value is a string, then the records will be returned read-only since they will
+ # have attributes that do not correspond to the table's columns.
# Pass <tt>:readonly => false</tt> to override.
# * <tt>:include</tt> - Names associations that should be loaded alongside. The symbols named refer
# to already defined associations. See eager loading under Associations.
- # * <tt>:select</tt> - By default, this is "*" as in "SELECT * FROM", but can be changed if you, for example, want to do a join but not
- # include the joined columns. Takes a string with the SELECT SQL fragment (e.g. "id, name").
- # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an alternate table name (or even the name
- # of a database view).
+ # * <tt>:select</tt> - By default, this is "*" as in "SELECT * FROM", but can be changed if you,
+ # for example, want to do a join but not include the joined columns. Takes a string with the SELECT SQL fragment (e.g. "id, name").
+ # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed
+ # to an alternate table name (or even the name of a database view).
# * <tt>:readonly</tt> - Mark the returned records read-only so they cannot be saved or updated.
# * <tt>:lock</tt> - An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE".
# <tt>:lock => true</tt> gives connection's default exclusive lock, usually "FOR UPDATE".
@@ -164,6 +169,8 @@ module ActiveRecord
# Person.exists?(['name LIKE ?', "%#{query}%"])
# Person.exists?
def exists?(id = nil)
+ id = id.id if ActiveRecord::Base === id
+
case id
when Array, Hash
where(id).exists?
@@ -279,6 +286,8 @@ module ActiveRecord
end
def find_one(id)
+ id = id.id if ActiveRecord::Base === id
+
record = where(primary_key.eq(id)).first
unless record
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 4692271266..e71f1cca72 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -11,91 +11,91 @@ module ActiveRecord
def includes(*args)
args.reject! { |a| a.blank? }
- clone.tap { |r| r.includes_values += args if args.present? }
+ clone.tap {|r| r.includes_values += args if args.present? }
end
def eager_load(*args)
- clone.tap { |r| r.eager_load_values += args if args.present? }
+ clone.tap {|r| r.eager_load_values += args if args.present? }
end
def preload(*args)
- clone.tap { |r| r.preload_values += args if args.present? }
+ clone.tap {|r| r.preload_values += args if args.present? }
end
def select(*args)
if block_given?
- to_a.select { |*block_args| yield(*block_args) }
+ to_a.select {|*block_args| yield(*block_args) }
else
- clone.tap { |r| r.select_values += args if args.present? }
+ clone.tap {|r| r.select_values += args if args.present? }
end
end
def group(*args)
- clone.tap { |r| r.group_values += args if args.present? }
+ clone.tap {|r| r.group_values += args.flatten if args.present? }
end
def order(*args)
- clone.tap { |r| r.order_values += args if args.present? }
+ clone.tap {|r| r.order_values += args if args.present? }
end
def reorder(*args)
- clone.tap { |r| r.order_values = args if args.present? }
+ clone.tap {|r| r.order_values = args if args.present? }
end
def joins(*args)
args.flatten!
- clone.tap { |r| r.joins_values += args if args.present? }
+ clone.tap {|r| r.joins_values += args if args.present? }
end
- def where(*args)
- value = build_where(*args)
- clone.tap { |r| r.where_values += Array.wrap(value) if value.present? }
+ def where(opts, *rest)
+ value = build_where(opts, rest)
+ value ? clone.tap {|r| r.where_values += Array.wrap(value) } : clone
end
def having(*args)
value = build_where(*args)
- clone.tap { |r| r.having_values += Array.wrap(value) if value.present? }
+ clone.tap {|r| r.having_values += Array.wrap(value) if value.present? }
end
def limit(value = true)
- clone.tap { |r| r.limit_value = value }
+ clone.tap {|r| r.limit_value = value }
end
def offset(value = true)
- clone.tap { |r| r.offset_value = value }
+ clone.tap {|r| r.offset_value = value }
end
def lock(locks = true)
case locks
when String, TrueClass, NilClass
- clone.tap { |r| r.lock_value = locks || true }
+ clone.tap {|r| r.lock_value = locks || true }
else
- clone.tap { |r| r.lock_value = false }
+ clone.tap {|r| r.lock_value = false }
end
end
def readonly(value = true)
- clone.tap { |r| r.readonly_value = value }
+ clone.tap {|r| r.readonly_value = value }
end
def create_with(value = true)
- clone.tap { |r| r.create_with_value = value }
+ clone.tap {|r| r.create_with_value = value }
end
def from(value = true)
- clone.tap { |r| r.from_value = value }
+ clone.tap {|r| r.from_value = value }
end
def extending(*modules, &block)
modules << Module.new(&block) if block_given?
- clone.tap { |r| r.send(:apply_modules, *modules) }
+ clone.tap {|r| r.send(:apply_modules, *modules) }
end
def reverse_order
order_clause = arel.send(:order_clauses).join(', ')
relation = except(:order)
- if order_clause.present?
+ unless order_clauses.blank?
relation.order(reverse_sql_order(order_clause))
else
relation.order("#{@klass.table_name}.#{@klass.primary_key} DESC")
@@ -129,7 +129,7 @@ module ActiveRecord
def build_arel
arel = table
- arel = build_joins(arel, @joins_values) if @joins_values.present?
+ arel = build_joins(arel, @joins_values) unless @joins_values.empty?
@where_values.uniq.each do |where|
next if where.blank?
@@ -143,36 +143,27 @@ module ActiveRecord
end
end
- arel = arel.having(*@having_values.uniq.select{|h| h.present?}) if @having_values.present?
+ arel = arel.having(*@having_values.uniq.select{|h| h.present?}) unless @having_values.empty?
- arel = arel.take(@limit_value) if @limit_value.present?
- arel = arel.skip(@offset_value) if @offset_value.present?
+ arel = arel.take(@limit_value) if @limit_value
+ arel = arel.skip(@offset_value) if @offset_value
- arel = arel.group(*@group_values.uniq.select{|g| g.present?}) if @group_values.present?
+ arel = arel.group(*@group_values.uniq.select{|g| g.present?}) unless @group_values.empty?
- arel = arel.order(*@order_values.uniq.select{|o| o.present?}) if @order_values.present?
+ arel = arel.order(*@order_values.uniq.select{|o| o.present?}) unless @order_values.empty?
arel = build_select(arel, @select_values.uniq)
- arel = arel.from(@from_value) if @from_value.present?
-
- case @lock_value
- when TrueClass
- arel = arel.lock
- when String
- arel = arel.lock(@lock_value)
- end if @lock_value.present?
+ arel = arel.from(@from_value) if @from_value
+ arel = arel.lock(@lock_value) if @lock_value
arel
end
- def build_where(*args)
- return if args.blank?
-
- opts = args.first
+ def build_where(opts, other = [])
case opts
when String, Array
- @klass.send(:sanitize_sql, args.size > 1 ? args : opts)
+ @klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))
when Hash
attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
PredicateBuilder.new(table.engine).build_from_hash(attributes, table)
@@ -230,7 +221,7 @@ module ActiveRecord
@implicit_readonly = false
# TODO: fix this ugly hack, we should refactor the callers to get an ARel compatible array.
# Before this change we were passing to ARel the last element only, and ARel is capable of handling an array
- if selects.all? { |s| s.is_a?(String) || !s.is_a?(Arel::Expression) } && !(selects.last =~ /^COUNT\(/)
+ if selects.all? {|s| s.is_a?(String) || !s.is_a?(Arel::Expression) } && !(selects.last =~ /^COUNT\(/)
arel.project(*selects)
else
arel.project(selects.last)
@@ -247,7 +238,7 @@ module ActiveRecord
end
def reverse_sql_order(order_query)
- order_query.to_s.split(/,/).each { |s|
+ order_query.split(',').each { |s|
if s.match(/\s(asc|ASC)$/)
s.gsub!(/\s(asc|ASC)$/, ' DESC')
elsif s.match(/\s(desc|DESC)$/)
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index e2783087ec..c1bc3214ea 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -2,7 +2,7 @@ require 'active_support/core_ext/object/blank'
module ActiveRecord
# = Active Record Schema
- #
+ #
# Allows programmers to programmatically define a schema in a portable
# DSL. This means you can define tables, indexes, etc. without using SQL
# directly, so your applications can more easily support multiple
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index a4757773d8..e9af20e1b6 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -8,13 +8,13 @@ module ActiveRecord
# output format (i.e., ActiveRecord::Schema).
class SchemaDumper #:nodoc:
private_class_method :new
-
+
##
# :singleton-method:
- # A list of tables which should not be dumped to the schema.
+ # A list of tables which should not be dumped to the schema.
# Acceptable values are strings as well as regexp.
# This setting is only used if ActiveRecord::Base.schema_format == :ruby
- cattr_accessor :ignore_tables
+ cattr_accessor :ignore_tables
@@ignore_tables = []
def self.dump(connection=ActiveRecord::Base.connection, stream=STDOUT)
@@ -71,7 +71,7 @@ HEADER
else
raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
end
- end
+ end
table(tbl, stream)
end
end
@@ -87,7 +87,7 @@ HEADER
elsif @connection.respond_to?(:primary_key)
pk = @connection.primary_key(table)
end
-
+
tbl.print " create_table #{table.inspect}"
if columns.detect { |c| c.name == pk }
if pk != 'id'
@@ -105,7 +105,7 @@ HEADER
next if column.name == pk
spec = {}
spec[:name] = column.name.inspect
-
+
# AR has an optimisation which handles zero-scale decimals as integers. This
# code ensures that the dumper still dumps the column as a decimal.
spec[:type] = if column.type == :integer && [/^numeric/, /^decimal/].any? { |e| e.match(column.sql_type) }
@@ -148,7 +148,7 @@ HEADER
tbl.puts " end"
tbl.puts
-
+
indexes(table, tbl)
tbl.rewind
@@ -158,7 +158,7 @@ HEADER
stream.puts "# #{e.message}"
stream.puts
end
-
+
stream
end
@@ -172,7 +172,7 @@ HEADER
value.inspect
end
end
-
+
def indexes(table, stream)
if (indexes = @connection.indexes(table)).any?
add_index_statements = indexes.map do |index|
diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb
index b88d550086..becde0fbfd 100644
--- a/activerecord/lib/active_record/session_store.rb
+++ b/activerecord/lib/active_record/session_store.rb
@@ -16,7 +16,7 @@ module ActiveRecord
# ActionController::SessionOverflowError will be raised.
#
# You may configure the table name, primary key, and data column.
- # For example, at the end of <tt>config/environment.rb</tt>:
+ # For example, at the end of <tt>config/application.rb</tt>:
#
# ActiveRecord::SessionStore::Session.table_name = 'legacy_session_table'
# ActiveRecord::SessionStore::Session.primary_key = 'session_id'
@@ -49,8 +49,34 @@ module ActiveRecord
# The example SqlBypass class is a generic SQL session store. You may
# use it as a basis for high-performance database-specific stores.
class SessionStore < ActionDispatch::Session::AbstractStore
+ module ClassMethods # :nodoc:
+ def marshal(data)
+ ActiveSupport::Base64.encode64(Marshal.dump(data)) if data
+ end
+
+ def unmarshal(data)
+ Marshal.load(ActiveSupport::Base64.decode64(data)) if data
+ end
+
+ def drop_table!
+ connection.execute "DROP TABLE #{table_name}"
+ end
+
+ def create_table!
+ connection.execute <<-end_sql
+ CREATE TABLE #{table_name} (
+ id #{connection.type_to_sql(:primary_key)},
+ #{connection.quote_column_name(session_id_column)} VARCHAR(255) UNIQUE,
+ #{connection.quote_column_name(data_column_name)} TEXT
+ )
+ end_sql
+ end
+ end
+
# The default Active Record class.
class Session < ActiveRecord::Base
+ extend ClassMethods
+
##
# :singleton-method:
# Customizable data column name. Defaults to 'data'.
@@ -62,7 +88,7 @@ module ActiveRecord
class << self
def data_column_size_limit
- @data_column_size_limit ||= columns_hash[@@data_column_name].limit
+ @data_column_size_limit ||= columns_hash[data_column_name].limit
end
# Hook to set up sessid compatibility.
@@ -71,29 +97,11 @@ module ActiveRecord
find_by_session_id(session_id)
end
- def marshal(data)
- ActiveSupport::Base64.encode64(Marshal.dump(data)) if data
- end
-
- def unmarshal(data)
- Marshal.load(ActiveSupport::Base64.decode64(data)) if data
- end
-
- def create_table!
- connection.execute <<-end_sql
- CREATE TABLE #{table_name} (
- id INTEGER PRIMARY KEY,
- #{connection.quote_column_name('session_id')} TEXT UNIQUE,
- #{connection.quote_column_name(@@data_column_name)} TEXT(255)
- )
- end_sql
- end
-
- def drop_table!
- connection.execute "DROP TABLE #{table_name}"
- end
-
private
+ def session_id_column
+ 'session_id'
+ end
+
# Compatibility with tables using sessid instead of session_id.
def setup_sessid_compatibility!
# Reset column info since it may be stale.
@@ -106,6 +114,8 @@ module ActiveRecord
define_method(:session_id) { sessid }
define_method(:session_id=) { |session_id| self.sessid = session_id }
else
+ class << self; remove_method :find_by_session_id; end
+
def self.find_by_session_id(session_id)
find :first, :conditions => {:session_id=>session_id}
end
@@ -113,6 +123,11 @@ module ActiveRecord
end
end
+ def initialize(attributes = nil)
+ @data = nil
+ super
+ end
+
# Lazy-unmarshal session state.
def data
@data ||= self.class.unmarshal(read_attribute(@@data_column_name)) || {}
@@ -122,22 +137,22 @@ module ActiveRecord
# Has the session been loaded yet?
def loaded?
- !!@data
+ @data
end
private
def marshal_data!
- return false if !loaded?
- write_attribute(@@data_column_name, self.class.marshal(self.data))
+ return false unless loaded?
+ write_attribute(@@data_column_name, self.class.marshal(data))
end
# Ensures that the data about to be stored in the database is not
# larger than the data storage column. Raises
# ActionController::SessionOverflowError.
def raise_on_session_data_overflow!
- return false if !loaded?
+ return false unless loaded?
limit = self.class.data_column_size_limit
- if loaded? and limit and read_attribute(@@data_column_name).size > limit
+ if limit and read_attribute(@@data_column_name).size > limit
raise ActionController::SessionOverflowError
end
end
@@ -162,6 +177,8 @@ module ActiveRecord
# binary session data in a +text+ column. For higher performance,
# store in a +blob+ column instead and forgo the Base64 encoding.
class SqlBypass
+ extend ClassMethods
+
##
# :singleton-method:
# Use the ActiveRecord::Base.connection by default.
@@ -186,6 +203,8 @@ module ActiveRecord
@@data_column = 'data'
class << self
+ alias :data_column_name :data_column
+
def connection
@@connection ||= ActiveRecord::Base.connection
end
@@ -196,43 +215,21 @@ module ActiveRecord
new(:session_id => session_id, :marshaled_data => record['data'])
end
end
-
- def marshal(data)
- ActiveSupport::Base64.encode64(Marshal.dump(data)) if data
- end
-
- def unmarshal(data)
- Marshal.load(ActiveSupport::Base64.decode64(data)) if data
- end
-
- def create_table!
- @@connection.execute <<-end_sql
- CREATE TABLE #{table_name} (
- id INTEGER PRIMARY KEY,
- #{@@connection.quote_column_name(session_id_column)} TEXT UNIQUE,
- #{@@connection.quote_column_name(data_column)} TEXT
- )
- end_sql
- end
-
- def drop_table!
- @@connection.execute "DROP TABLE #{table_name}"
- end
end
- attr_reader :session_id
+ attr_reader :session_id, :new_record
+ alias :new_record? :new_record
+
attr_writer :data
# Look for normal and marshaled data, self.find_by_session_id's way of
# telling us to postpone unmarshaling until the data is requested.
# We need to handle a normal data attribute in case of a new record.
def initialize(attributes)
- @session_id, @data, @marshaled_data = attributes[:session_id], attributes[:data], attributes[:marshaled_data]
- @new_record = @marshaled_data.nil?
- end
-
- def new_record?
- @new_record
+ @session_id = attributes[:session_id]
+ @data = attributes[:data]
+ @marshaled_data = attributes[:marshaled_data]
+ @new_record = @marshaled_data.nil?
end
# Lazy-unmarshal session state.
@@ -248,39 +245,41 @@ module ActiveRecord
end
def loaded?
- !!@data
+ @data
end
def save
- return false if !loaded?
+ return false unless loaded?
marshaled_data = self.class.marshal(data)
+ connect = connection
if @new_record
@new_record = false
- @@connection.update <<-end_sql, 'Create session'
- INSERT INTO #{@@table_name} (
- #{@@connection.quote_column_name(@@session_id_column)},
- #{@@connection.quote_column_name(@@data_column)} )
+ connect.update <<-end_sql, 'Create session'
+ INSERT INTO #{table_name} (
+ #{connect.quote_column_name(session_id_column)},
+ #{connect.quote_column_name(data_column)} )
VALUES (
- #{@@connection.quote(session_id)},
- #{@@connection.quote(marshaled_data)} )
+ #{connect.quote(session_id)},
+ #{connect.quote(marshaled_data)} )
end_sql
else
- @@connection.update <<-end_sql, 'Update session'
- UPDATE #{@@table_name}
- SET #{@@connection.quote_column_name(@@data_column)}=#{@@connection.quote(marshaled_data)}
- WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
+ connect.update <<-end_sql, 'Update session'
+ UPDATE #{table_name}
+ SET #{connect.quote_column_name(data_column)}=#{connect.quote(marshaled_data)}
+ WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id)}
end_sql
end
end
def destroy
- unless @new_record
- @@connection.delete <<-end_sql, 'Destroy session'
- DELETE FROM #{@@table_name}
- WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
- end_sql
- end
+ return if @new_record
+
+ connect = connection
+ connect.delete <<-end_sql, 'Destroy session'
+ DELETE FROM #{table_name}
+ WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id)}
+ end_sql
end
end
@@ -289,7 +288,7 @@ module ActiveRecord
cattr_accessor :session_class
self.session_class = Session
- SESSION_RECORD_KEY = 'rack.session.record'.freeze
+ SESSION_RECORD_KEY = 'rack.session.record'
private
def get_session(env, sid)
@@ -317,7 +316,7 @@ module ActiveRecord
sid
end
-
+
def destroy(env)
if sid = current_session_id(env)
Base.silence do
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 1075a60f07..5531d12a41 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -12,6 +12,19 @@ module ActiveRecord
# Timestamps are in the local timezone by default but you can use UTC by setting:
#
# <tt>ActiveRecord::Base.default_timezone = :utc</tt>
+ #
+ # == Time Zone aware attributes
+ #
+ # By default, ActiveRecord::Base keeps all the datetime columns time zone aware by executing following code.
+ #
+ # ActiveRecord::Base.time_zone_aware_attributes = true
+ #
+ # This feature can easily be turned off by assigning value <tt>false</tt> .
+ #
+ # If your attributes are time zone aware and you desire to skip time zone conversion for certain
+ # attributes then you can do following:
+ #
+ # Topic.skip_time_zone_conversion_for_attributes = [:written_on]
module Timestamp
extend ActiveSupport::Concern
@@ -19,35 +32,16 @@ module ActiveRecord
class_inheritable_accessor :record_timestamps, :instance_writer => false
self.record_timestamps = true
end
-
- # Saves the record with the updated_at/on attributes set to the current time.
- # If the save fails because of validation errors, an
- # ActiveRecord::RecordInvalid exception is raised. If an attribute name is passed,
- # that attribute is used for the touch instead of the updated_at/on attributes.
- #
- # Examples:
- #
- # product.touch # updates updated_at
- # product.touch(:designed_at) # updates the designed_at attribute
- def touch(attribute = nil)
- current_time = current_time_from_proper_timezone
-
- if attribute
- write_attribute(attribute, current_time)
- else
- timestamp_attributes_for_update_in_model.each { |column| write_attribute(column.to_s, current_time) }
- end
-
- save!
- end
private
+
def create #:nodoc:
if record_timestamps
current_time = current_time_from_proper_timezone
- write_attribute('created_at', current_time) if respond_to?(:created_at) && created_at.nil?
- write_attribute('created_on', current_time) if respond_to?(:created_on) && created_on.nil?
+ timestamp_attributes_for_create.each do |column|
+ write_attribute(column.to_s, current_time) if respond_to?(column) && self.send(column).nil?
+ end
timestamp_attributes_for_update_in_model.each do |column|
write_attribute(column.to_s, current_time) if self.send(column).nil?
@@ -58,22 +52,33 @@ module ActiveRecord
end
def update(*args) #:nodoc:
- record_update_timestamps
+ record_update_timestamps if !partial_updates? || changed?
super
end
- def record_update_timestamps
- if record_timestamps && (!partial_updates? || changed?)
- current_time = current_time_from_proper_timezone
- timestamp_attributes_for_update_in_model.each { |column| write_attribute(column.to_s, current_time) }
- true
- else
- false
+ def record_update_timestamps #:nodoc:
+ return unless record_timestamps
+ current_time = current_time_from_proper_timezone
+ timestamp_attributes_for_update_in_model.inject({}) do |hash, column|
+ hash[column.to_s] = write_attribute(column.to_s, current_time)
+ hash
end
end
def timestamp_attributes_for_update_in_model #:nodoc:
- [:updated_at, :updated_on].select { |elem| respond_to?(elem) }
+ timestamp_attributes_for_update.select { |elem| respond_to?(elem) }
+ end
+
+ def timestamp_attributes_for_update #:nodoc:
+ [:updated_at, :updated_on]
+ end
+
+ def timestamp_attributes_for_create #:nodoc:
+ [:created_at, :created_on]
+ end
+
+ def all_timestamp_attributes #:nodoc:
+ timestamp_attributes_for_update + timestamp_attributes_for_create
end
def current_time_from_proper_timezone #:nodoc:
diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb
index 0b0f5682aa..15b587de45 100644
--- a/activerecord/lib/active_record/validations/associated.rb
+++ b/activerecord/lib/active_record/validations/associated.rb
@@ -27,8 +27,9 @@ module ActiveRecord
#
# this would specify a circular dependency and cause infinite recursion.
#
- # NOTE: This validation will not fail if the association hasn't been assigned. If you want to ensure that the association
- # is both present and guaranteed to be valid, you also need to use +validates_presence_of+.
+ # NOTE: This validation will not fail if the association hasn't been assigned. If you want to
+ # ensure that the association is both present and guaranteed to be valid, you also need to
+ # use +validates_presence_of+.
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "is invalid")
@@ -44,4 +45,4 @@ module ActiveRecord
end
end
end
-end \ No newline at end of file
+end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 1c9ecc7b1b..bf863c7063 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -78,22 +78,25 @@ module ActiveRecord
end
module ClassMethods
- # Validates whether the value of the specified attributes are unique across the system. Useful for making sure that only one user
+ # Validates whether the value of the specified attributes are unique across the system.
+ # Useful for making sure that only one user
# can be named "davidhh".
#
# class Person < ActiveRecord::Base
# validates_uniqueness_of :user_name, :scope => :account_id
# end
#
- # It can also validate whether the value of the specified attributes are unique based on multiple scope parameters. For example,
- # making sure that a teacher can only be on the schedule once per semester for a particular class.
+ # It can also validate whether the value of the specified attributes are unique based on multiple
+ # scope parameters. For example, making sure that a teacher can only be on the schedule once
+ # per semester for a particular class.
#
# class TeacherSchedule < ActiveRecord::Base
# validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id]
# end
#
- # When the record is created, a check is performed to make sure that no record exists in the database with the given value for the specified
- # attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
+ # When the record is created, a check is performed to make sure that no record exists in the database
+ # with the given value for the specified attribute (that maps to a column). When the record is updated,
+ # the same check is made but disregarding the record itself.
#
# Configuration options:
# * <tt>:message</tt> - Specifies a custom error message (default is: "has already been taken").
@@ -102,11 +105,12 @@ module ActiveRecord
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
+ # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
+ # The method, proc or string should return or evaluate to a true or false value.
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
+ # not occur (e.g. <tt>:unless => :skip_validation</tt>, or
+ # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The method, proc or string should
+ # return or evaluate to a true or false value.
#
# === Concurrency and integrity
#
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index d18fed0131..a467ffa960 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -3,7 +3,7 @@ module ActiveRecord
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta4"
+ BUILD = "rc"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
index 6e6645511c..509baacaef 100644
--- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
@@ -42,7 +42,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
assert_equal "DROP TABLE `people`", drop_table(:people)
end
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
def test_create_mysql_database_with_encoding
assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt)
assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'})
@@ -101,6 +101,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
#we need to actually modify some data, so we make execute point to the original method
ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
alias_method :execute_with_stub, :execute
+ remove_method :execute
alias_method :execute, :execute_without_stub
end
yield
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
new file mode 100644
index 0000000000..a83399d0cd
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -0,0 +1,125 @@
+require "cases/helper"
+
+class ActiveSchemaTest < ActiveRecord::TestCase
+ def setup
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do
+ alias_method :execute_without_stub, :execute
+ remove_method :execute
+ def execute(sql, name = nil) return sql end
+ end
+ end
+
+ def teardown
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do
+ remove_method :execute
+ alias_method :execute, :execute_without_stub
+ end
+ end
+
+ def test_add_index
+ # add_index calls index_name_exists? which can't work since execute is stubbed
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:define_method, :index_name_exists?) do |*|
+ false
+ end
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`)"
+ assert_equal expected, add_index(:people, :last_name, :length => nil)
+
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10))"
+ assert_equal expected, add_index(:people, :last_name, :length => 10)
+
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15))"
+ assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15)
+
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`)"
+ assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15})
+
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10))"
+ assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10})
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:remove_method, :index_name_exists?)
+ end
+
+ def test_drop_table
+ assert_equal "DROP TABLE `people`", drop_table(:people)
+ end
+
+ if current_adapter?(:Mysql2Adapter)
+ def test_create_mysql_database_with_encoding
+ assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt)
+ assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'})
+ assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci})
+ end
+
+ def test_recreate_mysql_database_with_encoding
+ create_database(:luca, {:charset => 'latin1'})
+ assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, {:charset => 'latin1'})
+ end
+ end
+
+ def test_add_column
+ assert_equal "ALTER TABLE `people` ADD `last_name` varchar(255)", add_column(:people, :last_name, :string)
+ end
+
+ def test_add_column_with_limit
+ assert_equal "ALTER TABLE `people` ADD `key` varchar(32)", add_column(:people, :key, :string, :limit => 32)
+ end
+
+ def test_drop_table_with_specific_database
+ assert_equal "DROP TABLE `otherdb`.`people`", drop_table('otherdb.people')
+ end
+
+ def test_add_timestamps
+ with_real_execute do
+ begin
+ ActiveRecord::Base.connection.create_table :delete_me do |t|
+ end
+ ActiveRecord::Base.connection.add_timestamps :delete_me
+ assert column_present?('delete_me', 'updated_at', 'datetime')
+ assert column_present?('delete_me', 'created_at', 'datetime')
+ ensure
+ ActiveRecord::Base.connection.drop_table :delete_me rescue nil
+ end
+ end
+ end
+
+ def test_remove_timestamps
+ with_real_execute do
+ begin
+ ActiveRecord::Base.connection.create_table :delete_me do |t|
+ t.timestamps
+ end
+ ActiveRecord::Base.connection.remove_timestamps :delete_me
+ assert !column_present?('delete_me', 'updated_at', 'datetime')
+ assert !column_present?('delete_me', 'created_at', 'datetime')
+ ensure
+ ActiveRecord::Base.connection.drop_table :delete_me rescue nil
+ end
+ end
+ end
+
+ private
+ def with_real_execute
+ #we need to actually modify some data, so we make execute point to the original method
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do
+ alias_method :execute_with_stub, :execute
+ remove_method :execute
+ alias_method :execute, :execute_without_stub
+ end
+ yield
+ ensure
+ #before finishing, we restore the alias to the mock-up method
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do
+ remove_method :execute
+ alias_method :execute, :execute_with_stub
+ end
+ end
+
+
+ def method_missing(method_symbol, *arguments)
+ ActiveRecord::Base.connection.send(method_symbol, *arguments)
+ end
+
+ def column_present?(table_name, column_name, type)
+ results = ActiveRecord::Base.connection.select_all("SHOW FIELDS FROM #{table_name} LIKE '#{column_name}'")
+ results.first && results.first['Type'] == type
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
new file mode 100644
index 0000000000..b973da621b
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -0,0 +1,42 @@
+require "cases/helper"
+
+class MysqlConnectionTest < ActiveRecord::TestCase
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def test_no_automatic_reconnection_after_timeout
+ assert @connection.active?
+ @connection.update('set @@wait_timeout=1')
+ sleep 2
+ assert !@connection.active?
+ end
+
+ def test_successful_reconnection_after_timeout_with_manual_reconnect
+ assert @connection.active?
+ @connection.update('set @@wait_timeout=1')
+ sleep 2
+ @connection.reconnect!
+ assert @connection.active?
+ end
+
+ def test_successful_reconnection_after_timeout_with_verify
+ assert @connection.active?
+ @connection.update('set @@wait_timeout=1')
+ sleep 2
+ @connection.verify!
+ assert @connection.active?
+ end
+
+ private
+
+ def run_without_connection
+ original_connection = ActiveRecord::Base.remove_connection
+ begin
+ yield original_connection
+ ensure
+ ActiveRecord::Base.establish_connection(original_connection)
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
new file mode 100644
index 0000000000..90d8b0d923
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
@@ -0,0 +1,176 @@
+require "cases/helper"
+
+class Group < ActiveRecord::Base
+ Group.table_name = 'group'
+ belongs_to :select, :class_name => 'Select'
+ has_one :values
+end
+
+class Select < ActiveRecord::Base
+ Select.table_name = 'select'
+ has_many :groups
+end
+
+class Values < ActiveRecord::Base
+ Values.table_name = 'values'
+end
+
+class Distinct < ActiveRecord::Base
+ Distinct.table_name = 'distinct'
+ has_and_belongs_to_many :selects
+ has_many :values, :through => :groups
+end
+
+# a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with
+# reserved word names (ie: group, order, values, etc...)
+class MysqlReservedWordTest < ActiveRecord::TestCase
+ def setup
+ @connection = ActiveRecord::Base.connection
+
+ # we call execute directly here (and do similar below) because ActiveRecord::Base#create_table()
+ # will fail with these table names if these test cases fail
+
+ create_tables_directly 'group'=>'id int auto_increment primary key, `order` varchar(255), select_id int',
+ 'select'=>'id int auto_increment primary key',
+ 'values'=>'id int auto_increment primary key, group_id int',
+ 'distinct'=>'id int auto_increment primary key',
+ 'distincts_selects'=>'distinct_id int, select_id int'
+ end
+
+ def teardown
+ drop_tables_directly ['group', 'select', 'values', 'distinct', 'distincts_selects', 'order']
+ end
+
+ # create tables with reserved-word names and columns
+ def test_create_tables
+ assert_nothing_raised {
+ @connection.create_table :order do |t|
+ t.column :group, :string
+ end
+ }
+ end
+
+ # rename tables with reserved-word names
+ def test_rename_tables
+ assert_nothing_raised { @connection.rename_table(:group, :order) }
+ end
+
+ # alter column with a reserved-word name in a table with a reserved-word name
+ def test_change_columns
+ assert_nothing_raised { @connection.change_column_default(:group, :order, 'whatever') }
+ #the quoting here will reveal any double quoting issues in change_column's interaction with the column method in the adapter
+ assert_nothing_raised { @connection.change_column('group', 'order', :Int, :default => 0) }
+ assert_nothing_raised { @connection.rename_column(:group, :order, :values) }
+ end
+
+ # dump structure of table with reserved word name
+ def test_structure_dump
+ assert_nothing_raised { @connection.structure_dump }
+ end
+
+ # introspect table with reserved word name
+ def test_introspect
+ assert_nothing_raised { @connection.columns(:group) }
+ assert_nothing_raised { @connection.indexes(:group) }
+ end
+
+ #fixtures
+ self.use_instantiated_fixtures = true
+ self.use_transactional_fixtures = false
+
+ #fixtures :group
+
+ def test_fixtures
+ f = create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+
+ assert_nothing_raised {
+ f.each do |x|
+ x.delete_existing_fixtures
+ end
+ }
+
+ assert_nothing_raised {
+ f.each do |x|
+ x.insert_fixtures
+ end
+ }
+ end
+
+ #activerecord model class with reserved-word table name
+ def test_activerecord_model
+ create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ x = nil
+ assert_nothing_raised { x = Group.new }
+ x.order = 'x'
+ assert_nothing_raised { x.save }
+ x.order = 'y'
+ assert_nothing_raised { x.save }
+ assert_nothing_raised { y = Group.find_by_order('y') }
+ assert_nothing_raised { y = Group.find(1) }
+ x = Group.find(1)
+ end
+
+ # has_one association with reserved-word table name
+ def test_has_one_associations
+ create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ v = nil
+ assert_nothing_raised { v = Group.find(1).values }
+ assert_equal 2, v.id
+ end
+
+ # belongs_to association with reserved-word table name
+ def test_belongs_to_associations
+ create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ gs = nil
+ assert_nothing_raised { gs = Select.find(2).groups }
+ assert_equal gs.length, 2
+ assert(gs.collect{|x| x.id}.sort == [2, 3])
+ end
+
+ # has_and_belongs_to_many with reserved-word table name
+ def test_has_and_belongs_to_many
+ create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ s = nil
+ assert_nothing_raised { s = Distinct.find(1).selects }
+ assert_equal s.length, 2
+ assert(s.collect{|x|x.id}.sort == [1, 2])
+ end
+
+ # activerecord model introspection with reserved-word table and column names
+ def test_activerecord_introspection
+ assert_nothing_raised { Group.table_exists? }
+ assert_nothing_raised { Group.columns }
+ end
+
+ # Calculations
+ def test_calculations_work_with_reserved_words
+ assert_nothing_raised { Group.count }
+ end
+
+ def test_associations_work_with_reserved_words
+ assert_nothing_raised { Select.find(:all, :include => [:groups]) }
+ end
+
+ #the following functions were added to DRY test cases
+
+ private
+ # custom fixture loader, uses Fixtures#create_fixtures and appends base_path to the current file's path
+ def create_test_fixtures(*fixture_names)
+ Fixtures.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names)
+ end
+
+ # custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name
+ def drop_tables_directly(table_names, connection = @connection)
+ table_names.each do |name|
+ connection.execute("DROP TABLE IF EXISTS `#{name}`")
+ end
+ end
+
+ # custom create table, uses execute on connection to create a table, note: escapes table_name, does NOT escape columns
+ def create_tables_directly (tables, connection = @connection)
+ tables.each do |table_name, column_properties|
+ connection.execute("CREATE TABLE `#{table_name}` ( #{column_properties} )")
+ end
+ end
+
+end
diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index f106e14319..e4746d4aa3 100644
--- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -1,6 +1,6 @@
require 'cases/helper'
-class PostgresqlActiveSchemaTest < Test::Unit::TestCase
+class PostgresqlActiveSchemaTest < ActiveRecord::TestCase
def setup
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
alias_method :real_execute, :execute
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
new file mode 100644
index 0000000000..7b72151b57
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -0,0 +1,17 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class PostgreSQLAdapterTest < ActiveRecord::TestCase
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def test_table_alias_length
+ assert_nothing_raised do
+ @connection.table_alias_length
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb b/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb
index 2505372b7e..ce0b2f5f5b 100644
--- a/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb
@@ -103,17 +103,107 @@ module ActiveRecord
end
end
+ def test_columns
+ columns = @ctx.columns('items').sort_by { |x| x.name }
+ assert_equal 2, columns.length
+ assert_equal %w{ id number }.sort, columns.map { |x| x.name }
+ assert_equal [nil, nil], columns.map { |x| x.default }
+ assert_equal [true, true], columns.map { |x| x.null }
+ end
+
+ def test_columns_with_default
+ @ctx.execute <<-eosql
+ CREATE TABLE columns_with_default (
+ id integer PRIMARY KEY AUTOINCREMENT,
+ number integer default 10
+ )
+ eosql
+ column = @ctx.columns('columns_with_default').find { |x|
+ x.name == 'number'
+ }
+ assert_equal 10, column.default
+ end
+
+ def test_columns_with_not_null
+ @ctx.execute <<-eosql
+ CREATE TABLE columns_with_default (
+ id integer PRIMARY KEY AUTOINCREMENT,
+ number integer not null
+ )
+ eosql
+ column = @ctx.columns('columns_with_default').find { |x|
+ x.name == 'number'
+ }
+ assert !column.null, "column should not be null"
+ end
+
+ def test_indexes_logs
+ intercept_logs_on @ctx
+ assert_difference('@ctx.logged.length') do
+ @ctx.indexes('items')
+ end
+ assert_match(/items/, @ctx.logged.last.first)
+ end
+
+ def test_no_indexes
+ assert_equal [], @ctx.indexes('items')
+ end
+
+ def test_index
+ @ctx.add_index 'items', 'id', :unique => true, :name => 'fun'
+ index = @ctx.indexes('items').find { |idx| idx.name == 'fun' }
+
+ assert_equal 'items', index.table
+ assert index.unique, 'index is unique'
+ assert_equal ['id'], index.columns
+ end
+
+ def test_non_unique_index
+ @ctx.add_index 'items', 'id', :name => 'fun'
+ index = @ctx.indexes('items').find { |idx| idx.name == 'fun' }
+ assert !index.unique, 'index is not unique'
+ end
+
+ def test_compound_index
+ @ctx.add_index 'items', %w{ id number }, :name => 'fun'
+ index = @ctx.indexes('items').find { |idx| idx.name == 'fun' }
+ assert_equal %w{ id number }.sort, index.columns.sort
+ end
+
+ def test_primary_key
+ assert_equal 'id', @ctx.primary_key('items')
+
+ @ctx.execute <<-eosql
+ CREATE TABLE foos (
+ internet integer PRIMARY KEY AUTOINCREMENT,
+ number integer not null
+ )
+ eosql
+ assert_equal 'internet', @ctx.primary_key('foos')
+ end
+
+ def test_no_primary_key
+ @ctx.execute 'CREATE TABLE failboat (number integer not null)'
+ assert_nil @ctx.primary_key('failboat')
+ end
+
+ private
+
def assert_logged logs
+ intercept_logs_on @ctx
+ yield
+ assert_equal logs, @ctx.logged
+ end
+
+ def intercept_logs_on ctx
@ctx.extend(Module.new {
- attr_reader :logged
+ attr_accessor :logged
def log sql, name
- @logged ||= []
@logged << [sql, name]
yield
end
})
- yield
- assert_equal logs, @ctx.logged
+ @ctx.logged = []
end
end
end
diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb
index 74588b4f47..9e285e57dc 100644
--- a/activerecord/test/cases/aggregations_test.rb
+++ b/activerecord/test/cases/aggregations_test.rb
@@ -125,7 +125,7 @@ class OverridingAggregationsTest < ActiveRecord::TestCase
class Name; end
class DifferentName; end
- class Person < ActiveRecord::Base
+ class Person < ActiveRecord::Base
composed_of :composed_of, :mapping => %w(person_first_name first_name)
end
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index 665c387d5d..588adc38e3 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -28,7 +28,7 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_equal 7, ActiveRecord::Migrator::current_version
end
- def test_schema_raises_an_error_for_invalid_column_ntype
+ def test_schema_raises_an_error_for_invalid_column_type
assert_raise NoMethodError do
ActiveRecord::Schema.define(:version => 8) do
create_table :vegetables do |t|
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index fb1e6e7e70..a1ce9b1689 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -5,8 +5,6 @@ require 'models/company'
require 'models/topic'
require 'models/reply'
require 'models/computer'
-require 'models/customer'
-require 'models/order'
require 'models/post'
require 'models/author'
require 'models/tag'
@@ -34,7 +32,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_belongs_to_with_primary_key_joins_on_correct_column
sql = Client.joins(:firm_with_primary_key).to_sql
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
assert_no_match(/`firm_with_primary_keys_companies`\.`id`/, sql)
assert_match(/`firm_with_primary_keys_companies`\.`name`/, sql)
elsif current_adapter?(:OracleAdapter)
@@ -217,6 +215,10 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
r1.topic = Topic.find(t2.id)
+ assert_no_queries do
+ r1.topic = t2
+ end
+
assert r1.save
assert_equal 0, Topic.find(t1.id).replies.size
assert_equal 1, Topic.find(t2.id).replies.size
diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb
index 91b1af125e..15537d6940 100644
--- a/activerecord/test/cases/associations/callbacks_test.rb
+++ b/activerecord/test/cases/associations/callbacks_test.rb
@@ -1,8 +1,6 @@
require "cases/helper"
require 'models/post'
-require 'models/comment'
require 'models/author'
-require 'models/category'
require 'models/project'
require 'models/developer'
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index 9c5dcc2ad9..b93e49613d 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -2,7 +2,6 @@ require "cases/helper"
require 'models/post'
require 'models/comment'
require 'models/author'
-require 'models/category'
require 'models/categorization'
require 'models/company'
require 'models/topic'
@@ -46,6 +45,13 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
assert_equal people(:michael), Person.eager_load(:primary_contact => :primary_contact).where('primary_contacts_people_2.first_name = ?', 'Susan').order('people.id').first
end
+ def test_eager_association_loading_with_join_for_count
+ authors = Author.joins(:special_posts).includes([:posts, :categorizations])
+
+ assert_nothing_raised { authors.count }
+ assert_queries(3) { authors.all }
+ end
+
def test_eager_association_loading_with_cascaded_two_levels_with_two_has_many_associations
authors = Author.find(:all, :include=>{:posts=>[:comments, :categorizations]}, :order=>"authors.id")
assert_equal 2, authors.size
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index b11969a841..ed7d9a782c 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -2,20 +2,14 @@ require "cases/helper"
require 'models/developer'
require 'models/project'
require 'models/company'
-require 'models/topic'
-require 'models/reply'
-require 'models/computer'
require 'models/customer'
require 'models/order'
require 'models/categorization'
require 'models/category'
require 'models/post'
require 'models/author'
-require 'models/comment'
require 'models/tag'
require 'models/tagging'
-require 'models/person'
-require 'models/reader'
require 'models/parrot'
require 'models/pirate'
require 'models/treasure'
@@ -24,6 +18,8 @@ require 'models/club'
require 'models/member'
require 'models/membership'
require 'models/sponsor'
+require 'models/country'
+require 'models/treaty'
require 'active_support/core_ext/string/conversions'
class ProjectWithAfterCreateHook < ActiveRecord::Base
@@ -83,6 +79,60 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects,
:parrots, :pirates, :treasures, :price_estimates, :tags, :taggings
+ def setup_data_for_habtm_case
+ ActiveRecord::Base.connection.execute('delete from countries_treaties')
+
+ country = Country.new(:name => 'India')
+ country.country_id = 'c1'
+ country.save!
+
+ treaty = Treaty.new(:name => 'peace')
+ treaty.treaty_id = 't1'
+ country.treaties << treaty
+ end
+
+ def test_should_property_quote_string_primary_keys
+ setup_data_for_habtm_case
+
+ con = ActiveRecord::Base.connection
+ sql = 'select * from countries_treaties'
+ record = con.select_rows(sql).last
+ assert_equal 'c1', record[0]
+ assert_equal 't1', record[1]
+ end
+
+ def test_should_record_timestamp_for_join_table
+ setup_data_for_habtm_case
+
+ con = ActiveRecord::Base.connection
+ sql = 'select * from countries_treaties'
+ record = con.select_rows(sql).last
+ assert_not_nil record[2]
+ assert_not_nil record[3]
+ if current_adapter?(:Mysql2Adapter)
+ assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[2].to_s(:db)
+ assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[3].to_s(:db)
+ else
+ assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[2]
+ assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[3]
+ end
+ end
+
+ def test_should_record_timestamp_for_join_table_only_if_timestamp_should_be_recorded
+ begin
+ Treaty.record_timestamps = false
+ setup_data_for_habtm_case
+
+ con = ActiveRecord::Base.connection
+ sql = 'select * from countries_treaties'
+ record = con.select_rows(sql).last
+ assert_nil record[2]
+ assert_nil record[3]
+ ensure
+ Treaty.record_timestamps = true
+ end
+ end
+
def test_has_and_belongs_to_many
david = Developer.find(1)
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index a52cedd8c2..ac2021c369 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -11,53 +11,50 @@ require 'models/comment'
require 'models/person'
require 'models/reader'
require 'models/tagging'
+require 'models/invoice'
+require 'models/line_item'
-class HasManyAssociationsTest < ActiveRecord::TestCase
- fixtures :accounts, :categories, :companies, :developers, :projects,
- :developers_projects, :topics, :authors, :comments,
- :people, :posts, :readers, :taggings
-
- def setup
- Client.destroyed_client_ids.clear
+class HasManyAssociationsTestForCountWithFinderSql < ActiveRecord::TestCase
+ class Invoice < ActiveRecord::Base
+ has_many :custom_line_items, :class_name => 'LineItem', :finder_sql => "SELECT line_items.* from line_items"
end
+ def test_should_fail
+ assert_raise(ArgumentError) do
+ Invoice.create.custom_line_items.count(:conditions => {:amount => 0})
+ end
+ end
+end
- def test_create_by
- person = Person.create! :first_name => 'tenderlove'
- post = Post.find :first
-
- assert_equal [], person.readers
- assert_nil person.readers.find_by_post_id post.id
-
- reader = person.readers.create_by_post_id post.id
-
- assert_equal 1, person.readers.count
- assert_equal 1, person.readers.length
- assert_equal post, person.readers.first.post
- assert_equal person, person.readers.first.person
+class HasManyAssociationsTestForCountWithCountSql < ActiveRecord::TestCase
+ class Invoice < ActiveRecord::Base
+ has_many :custom_line_items, :class_name => 'LineItem', :counter_sql => "SELECT COUNT(*) line_items.* from line_items"
end
+ def test_should_fail
+ assert_raise(ArgumentError) do
+ Invoice.create.custom_line_items.count(:conditions => {:amount => 0})
+ end
+ end
+end
- def test_create_by_multi
- person = Person.create! :first_name => 'tenderlove'
- post = Post.find :first
- assert_equal [], person.readers
- reader = person.readers.create_by_post_id_and_skimmer post.id, false
+class HasManyAssociationsTest < ActiveRecord::TestCase
+ fixtures :accounts, :categories, :companies, :developers, :projects,
+ :developers_projects, :topics, :authors, :comments,
+ :people, :posts, :readers, :taggings
- assert_equal 1, person.readers.count
- assert_equal 1, person.readers.length
- assert_equal post, person.readers.first.post
- assert_equal person, person.readers.first.person
+ def setup
+ Client.destroyed_client_ids.clear
end
- def test_find_or_create_by
- person = Person.create! :first_name => 'tenderlove'
- post = Post.find :first
+ def test_create_resets_cached_counters
+ person = Person.create!(:first_name => 'tenderlove')
+ post = Post.first
assert_equal [], person.readers
- assert_nil person.readers.find_by_post_id post.id
+ assert_nil person.readers.find_by_post_id(post.id)
- reader = person.readers.find_or_create_by_post_id post.id
+ reader = person.readers.create(:post_id => post.id)
assert_equal 1, person.readers.count
assert_equal 1, person.readers.length
@@ -65,16 +62,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal person, person.readers.first.person
end
- def test_find_or_create
+ def test_find_or_create_by_resets_cached_counters
person = Person.create! :first_name => 'tenderlove'
- post = Post.find :first
+ post = Post.first
assert_equal [], person.readers
- assert_nil person.readers.find(:first, :conditions => {
- :post_id => post.id
- })
+ assert_nil person.readers.find_by_post_id(post.id)
- reader = person.readers.find_or_create :post_id => post.id
+ reader = person.readers.find_or_create_by_post_id(post.id)
assert_equal 1, person.readers.count
assert_equal 1, person.readers.length
@@ -82,7 +77,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal person, person.readers.first.person
end
-
def force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.each {|f| }
end
@@ -173,6 +167,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
companies(:first_firm).readonly_clients.find(:all).each { |c| assert c.readonly? }
end
+ def test_dynamic_find_or_create_from_two_attributes_using_an_association
+ author = authors(:david)
+ number_of_posts = Post.count
+ another = author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body")
+ assert_equal number_of_posts + 1, Post.count
+ assert_equal another, author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body")
+ assert !another.new_record?
+ end
+
def test_cant_save_has_many_readonly_association
authors(:david).readonly_comments.each { |c| assert_raise(ActiveRecord::ReadOnlyRecord) { c.save! } }
authors(:david).readonly_comments.each { |c| assert c.readonly? }
@@ -549,7 +552,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert the_client.new_record?
end
- def test_find_or_create
+ def test_find_or_create_updates_size
number_of_clients = companies(:first_firm).clients.size
the_client = companies(:first_firm).clients.find_or_create_by_name("Yet another client")
assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index e4dd810732..0eaadac5ae 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -14,9 +14,14 @@ require 'models/toy'
require 'models/contract'
require 'models/company'
require 'models/developer'
+require 'models/subscriber'
+require 'models/book'
+require 'models/subscription'
class HasManyThroughAssociationsTest < ActiveRecord::TestCase
- fixtures :posts, :readers, :people, :comments, :authors, :owners, :pets, :toys, :jobs, :references, :companies
+ fixtures :posts, :readers, :people, :comments, :authors,
+ :owners, :pets, :toys, :jobs, :references, :companies,
+ :subscribers, :books, :subscriptions, :developers
# Dummies to force column loads so query counts are clean.
def setup
@@ -383,4 +388,37 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
lambda { authors(:david).very_special_comments.delete(authors(:david).very_special_comments.first) },
].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) }
end
+
+ def test_collection_singular_ids_getter_with_string_primary_keys
+ book = books(:awdr)
+ assert_equal 2, book.subscriber_ids.size
+ assert_equal [subscribers(:first).nick, subscribers(:second).nick].sort, book.subscriber_ids.sort
+ end
+
+ def test_collection_singular_ids_setter
+ company = companies(:rails_core)
+ dev = Developer.find(:first)
+
+ company.developer_ids = [dev.id]
+ assert_equal [dev], company.developers
+ end
+
+ def test_collection_singular_ids_setter_with_string_primary_keys
+ assert_nothing_raised do
+ book = books(:awdr)
+ book.subscriber_ids = [subscribers(:second).nick]
+ assert_equal [subscribers(:second)], book.subscribers(true)
+
+ book.subscriber_ids = []
+ assert_equal [], book.subscribers(true)
+ end
+
+ end
+
+ def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set
+ company = companies(:rails_core)
+ ids = [Developer.find(:first).id, -9999]
+ assert_raises(ActiveRecord::RecordNotFound) {company.developer_ids= ids}
+ 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 178c57435b..3fcd150422 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -6,9 +6,12 @@ require 'models/membership'
require 'models/sponsor'
require 'models/organization'
require 'models/member_detail'
+require 'models/minivan'
+require 'models/dashboard'
+require 'models/speedometer'
class HasOneThroughAssociationsTest < ActiveRecord::TestCase
- fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations
+ fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations, :minivans, :dashboards, :speedometers
def setup
@member = members(:groucho)
@@ -202,4 +205,11 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
Club.find(@club.id, :include => :sponsored_member).save!
end
end
+
+ def test_value_is_properly_quoted
+ minivan = Minivan.find('m1')
+ assert_nothing_raised do
+ minivan.dashboard
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 34d24a2948..fa5c2e49df 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -412,7 +412,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase
i = interests(:trainspotting)
m = i.man
assert_not_nil m.interests
- iz = m.interests.detect {|iz| iz.id == i.id}
+ iz = m.interests.detect { |_iz| _iz.id == i.id}
assert_not_nil iz
assert_equal i.topic, iz.topic, "Interest topics should be the same before changes to child"
i.topic = 'Eating cheese with a spoon'
@@ -516,7 +516,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
i = interests(:llama_wrangling)
m = i.polymorphic_man
assert_not_nil m.polymorphic_interests
- iz = m.polymorphic_interests.detect {|iz| iz.id == i.id}
+ iz = m.polymorphic_interests.detect { |_iz| _iz.id == i.id}
assert_not_nil iz
assert_equal i.topic, iz.topic, "Interest topics should be the same before changes to child"
i.topic = 'Eating cheese with a spoon'
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 4ae776c35a..b31611e27a 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -2,11 +2,6 @@ require "cases/helper"
require 'models/developer'
require 'models/project'
require 'models/company'
-require 'models/topic'
-require 'models/reply'
-require 'models/computer'
-require 'models/customer'
-require 'models/order'
require 'models/categorization'
require 'models/category'
require 'models/post'
@@ -17,18 +12,66 @@ require 'models/tagging'
require 'models/person'
require 'models/reader'
require 'models/parrot'
-require 'models/pirate'
-require 'models/treasure'
-require 'models/price_estimate'
-require 'models/club'
-require 'models/member'
-require 'models/membership'
-require 'models/sponsor'
+require 'models/ship_part'
+require 'models/ship'
+require 'models/liquid'
+require 'models/molecule'
+require 'models/electron'
class AssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :developers, :projects, :developers_projects,
:computers, :people, :readers
+ def test_eager_loading_should_not_change_count_of_children
+ liquid = Liquid.create(:name => 'salty')
+ molecule = liquid.molecules.create(:name => 'molecule_1')
+ molecule.electrons.create(:name => 'electron_1')
+ molecule.electrons.create(:name => 'electron_2')
+
+ liquids = Liquid.includes(:molecules => :electrons).where('molecules.id is not null')
+ assert_equal 1, liquids[0].molecules.length
+ end
+
+ def test_clear_association_cache_stored
+ firm = Firm.find(1)
+ assert_kind_of Firm, firm
+
+ firm.clear_association_cache
+ assert_equal Firm.find(1).clients.collect{ |x| x.name }.sort, firm.clients.collect{ |x| x.name }.sort
+ end
+
+ def test_clear_association_cache_new_record
+ firm = Firm.new
+ client_stored = Client.find(3)
+ client_new = Client.new
+ client_new.name = "The Joneses"
+ clients = [ client_stored, client_new ]
+
+ firm.clients << clients
+ assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set
+
+ firm.clear_association_cache
+ assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set
+ end
+
+ def test_loading_the_association_target_should_keep_child_records_marked_for_destruction
+ ship = Ship.create!(:name => "The good ship Dollypop")
+ part = ship.parts.create!(:name => "Mast")
+ part.mark_for_destruction
+ ship.parts.send(:load_target)
+ assert ship.parts[0].marked_for_destruction?
+ end
+
+ def test_loading_the_association_target_should_load_most_recent_attributes_for_child_records_marked_for_destruction
+ ship = Ship.create!(:name => "The good ship Dollypop")
+ part = ship.parts.create!(:name => "Mast")
+ part.mark_for_destruction
+ ShipPart.find(part.id).update_attribute(:name, 'Deck')
+ ship.parts.send(:load_target)
+ assert_equal 'Deck', ship.parts[0].name
+ end
+
+
def test_include_with_order_works
assert_nothing_raised {Account.find(:first, :order => 'id', :include => :firm)}
assert_nothing_raised {Account.find(:first, :order => :id, :include => :firm)}
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index d59fa0a632..2c069cd8a5 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -1,9 +1,15 @@
require "cases/helper"
-require 'models/topic'
require 'models/minimalistic'
+require 'models/developer'
+require 'models/auto_id'
+require 'models/computer'
+require 'models/topic'
+require 'models/company'
+require 'models/category'
+require 'models/reply'
class AttributeMethodsTest < ActiveRecord::TestCase
- fixtures :topics
+ fixtures :topics, :developers, :companies, :computers
def setup
@old_matchers = ActiveRecord::Base.send(:attribute_method_matchers).dup
@@ -16,6 +22,276 @@ class AttributeMethodsTest < ActiveRecord::TestCase
ActiveRecord::Base.send(:attribute_method_matchers).concat(@old_matchers)
end
+ def test_attribute_present
+ t = Topic.new
+ t.title = "hello there!"
+ t.written_on = Time.now
+ assert t.attribute_present?("title")
+ assert t.attribute_present?("written_on")
+ assert !t.attribute_present?("content")
+ end
+
+ def test_attribute_keys_on_new_instance
+ t = Topic.new
+ assert_equal nil, t.title, "The topics table has a title column, so it should be nil"
+ assert_raise(NoMethodError) { t.title2 }
+ end
+
+ def test_boolean_attributes
+ assert ! Topic.find(1).approved?
+ assert Topic.find(2).approved?
+ end
+
+ def test_set_attributes
+ topic = Topic.find(1)
+ topic.attributes = { "title" => "Budget", "author_name" => "Jason" }
+ topic.save
+ assert_equal("Budget", topic.title)
+ assert_equal("Jason", topic.author_name)
+ assert_equal(topics(:first).author_email_address, Topic.find(1).author_email_address)
+ end
+
+ def test_set_attributes_without_hash
+ topic = Topic.new
+ assert_nothing_raised { topic.attributes = '' }
+ end
+
+ def test_integers_as_nil
+ test = AutoId.create('value' => '')
+ assert_nil AutoId.find(test.id).value
+ end
+
+ def test_set_attributes_with_block
+ topic = Topic.new do |t|
+ t.title = "Budget"
+ t.author_name = "Jason"
+ end
+
+ assert_equal("Budget", topic.title)
+ assert_equal("Jason", topic.author_name)
+ end
+
+ def test_respond_to?
+ topic = Topic.find(1)
+ assert_respond_to topic, "title"
+ assert_respond_to topic, "title?"
+ assert_respond_to topic, "title="
+ assert_respond_to topic, :title
+ assert_respond_to topic, :title?
+ assert_respond_to topic, :title=
+ assert_respond_to topic, "author_name"
+ assert_respond_to topic, "attribute_names"
+ assert !topic.respond_to?("nothingness")
+ assert !topic.respond_to?(:nothingness)
+ end
+
+ def test_array_content
+ topic = Topic.new
+ topic.content = %w( one two three )
+ topic.save
+
+ assert_equal(%w( one two three ), Topic.find(topic.id).content)
+ end
+
+ def test_read_attributes_before_type_cast
+ category = Category.new({:name=>"Test categoty", :type => nil})
+ category_attrs = {"name"=>"Test categoty", "type" => nil, "categorizations_count" => nil}
+ assert_equal category_attrs , category.attributes_before_type_cast
+ end
+
+ if current_adapter?(:MysqlAdapter)
+ def test_read_attributes_before_type_cast_on_boolean
+ bool = Booleantest.create({ "value" => false })
+ assert_equal "0", bool.reload.attributes_before_type_cast["value"]
+ end
+ end
+
+ unless current_adapter?(:Mysql2Adapter)
+ def test_read_attributes_before_type_cast_on_datetime
+ developer = Developer.find(:first)
+ # Oracle adapter returns Time before type cast
+ unless current_adapter?(:OracleAdapter)
+ assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"]
+ else
+ assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"].to_s(:db)
+
+ developer.created_at = "345643456"
+ assert_equal developer.created_at_before_type_cast, "345643456"
+ assert_equal developer.created_at, nil
+
+ developer.created_at = "2010-03-21T21:23:32+01:00"
+ assert_equal developer.created_at_before_type_cast, "2010-03-21T21:23:32+01:00"
+ assert_equal developer.created_at, Time.parse("2010-03-21T21:23:32+01:00")
+ end
+ end
+ end
+
+ def test_hash_content
+ topic = Topic.new
+ topic.content = { "one" => 1, "two" => 2 }
+ topic.save
+
+ assert_equal 2, Topic.find(topic.id).content["two"]
+
+ topic.content_will_change!
+ topic.content["three"] = 3
+ topic.save
+
+ assert_equal 3, Topic.find(topic.id).content["three"]
+ end
+
+ def test_update_array_content
+ topic = Topic.new
+ topic.content = %w( one two three )
+
+ topic.content.push "four"
+ assert_equal(%w( one two three four ), topic.content)
+
+ topic.save
+
+ topic = Topic.find(topic.id)
+ topic.content << "five"
+ assert_equal(%w( one two three four five ), topic.content)
+ end
+
+ def test_case_sensitive_attributes_hash
+ # DB2 is not case-sensitive
+ return true if current_adapter?(:DB2Adapter)
+
+ assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.find(:first).attributes
+ end
+
+ def test_hashes_not_mangled
+ new_topic = { :title => "New Topic" }
+ new_topic_values = { :title => "AnotherTopic" }
+
+ topic = Topic.new(new_topic)
+ assert_equal new_topic[:title], topic.title
+
+ topic.attributes= new_topic_values
+ assert_equal new_topic_values[:title], topic.title
+ end
+
+ def test_create_through_factory
+ topic = Topic.create("title" => "New Topic")
+ topicReloaded = Topic.find(topic.id)
+ assert_equal(topic, topicReloaded)
+ end
+
+ def test_write_attribute
+ topic = Topic.new
+ topic.send(:write_attribute, :title, "Still another topic")
+ assert_equal "Still another topic", topic.title
+
+ topic.send(:write_attribute, "title", "Still another topic: part 2")
+ assert_equal "Still another topic: part 2", topic.title
+ end
+
+ def test_read_attribute
+ topic = Topic.new
+ topic.title = "Don't change the topic"
+ assert_equal "Don't change the topic", topic.send(:read_attribute, "title")
+ assert_equal "Don't change the topic", topic["title"]
+
+ assert_equal "Don't change the topic", topic.send(:read_attribute, :title)
+ assert_equal "Don't change the topic", topic[:title]
+ end
+
+ def test_read_attribute_when_false
+ topic = topics(:first)
+ topic.approved = false
+ assert !topic.approved?, "approved should be false"
+ topic.approved = "false"
+ assert !topic.approved?, "approved should be false"
+ end
+
+ def test_read_attribute_when_true
+ topic = topics(:first)
+ topic.approved = true
+ assert topic.approved?, "approved should be true"
+ topic.approved = "true"
+ assert topic.approved?, "approved should be true"
+ end
+
+ def test_read_write_boolean_attribute
+ topic = Topic.new
+ # puts ""
+ # puts "New Topic"
+ # puts topic.inspect
+ topic.approved = "false"
+ # puts "Expecting false"
+ # puts topic.inspect
+ assert !topic.approved?, "approved should be false"
+ topic.approved = "false"
+ # puts "Expecting false"
+ # puts topic.inspect
+ assert !topic.approved?, "approved should be false"
+ topic.approved = "true"
+ # puts "Expecting true"
+ # puts topic.inspect
+ assert topic.approved?, "approved should be true"
+ topic.approved = "true"
+ # puts "Expecting true"
+ # puts topic.inspect
+ assert topic.approved?, "approved should be true"
+ # puts ""
+ end
+
+ def test_query_attribute_string
+ [nil, "", " "].each do |value|
+ assert_equal false, Topic.new(:author_name => value).author_name?
+ end
+
+ assert_equal true, Topic.new(:author_name => "Name").author_name?
+ end
+
+ def test_query_attribute_number
+ [nil, 0, "0"].each do |value|
+ assert_equal false, Developer.new(:salary => value).salary?
+ end
+
+ assert_equal true, Developer.new(:salary => 1).salary?
+ assert_equal true, Developer.new(:salary => "1").salary?
+ end
+
+ def test_query_attribute_boolean
+ [nil, "", false, "false", "f", 0].each do |value|
+ assert_equal false, Topic.new(:approved => value).approved?
+ end
+
+ [true, "true", "1", 1].each do |value|
+ assert_equal true, Topic.new(:approved => value).approved?
+ end
+ end
+
+ def test_query_attribute_with_custom_fields
+ object = Company.find_by_sql(<<-SQL).first
+ SELECT c1.*, c2.ruby_type as string_value, c2.rating as int_value
+ FROM companies c1, companies c2
+ WHERE c1.firm_id = c2.id
+ AND c1.id = 2
+ SQL
+
+ assert_equal "Firm", object.string_value
+ assert object.string_value?
+
+ object.string_value = " "
+ assert !object.string_value?
+
+ assert_equal 1, object.int_value.to_i
+ assert object.int_value?
+
+ object.int_value = "0"
+ assert !object.int_value?
+ end
+
+ def test_non_attribute_access_and_assignment
+ topic = Topic.new
+ assert !topic.respond_to?("mumbo")
+ assert_raise(NoMethodError) { topic.mumbo }
+ assert_raise(NoMethodError) { topic.mumbo = 5 }
+ end
+
def test_undeclared_attribute_method_does_not_affect_respond_to_and_method_missing
topic = @target.new(:title => 'Budget')
assert topic.respond_to?('title')
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 3b89c12a3f..49e7147773 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -13,6 +13,8 @@ require 'models/post'
require 'models/reader'
require 'models/ship'
require 'models/ship_part'
+require 'models/tag'
+require 'models/tagging'
require 'models/treasure'
require 'models/company'
@@ -171,7 +173,7 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
end
class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
- fixtures :companies
+ fixtures :companies, :posts, :tags, :taggings
def test_should_save_parent_but_not_invalid_child
client = Client.new(:name => 'Joe (the Plumber)')
@@ -312,6 +314,12 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
assert_equal num_orders +1, Order.count
assert_equal num_customers +2, Customer.count
end
+
+ def test_store_association_with_a_polymorphic_relationship
+ num_tagging = Tagging.count
+ tags(:misc).create_tagging(:taggable => posts(:thinking))
+ assert_equal num_tagging +1, Tagging.count
+ end
end
class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index a4cf5120e1..ca397d3847 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -52,357 +52,6 @@ class BasicsTest < ActiveRecord::TestCase
assert Topic.table_exists?
end
- def test_set_attributes
- topic = Topic.find(1)
- topic.attributes = { "title" => "Budget", "author_name" => "Jason" }
- topic.save
- assert_equal("Budget", topic.title)
- assert_equal("Jason", topic.author_name)
- assert_equal(topics(:first).author_email_address, Topic.find(1).author_email_address)
- end
-
- def test_set_attributes_without_hash
- topic = Topic.new
- assert_nothing_raised do
- topic.attributes = ''
- end
- end
-
- def test_integers_as_nil
- test = AutoId.create('value' => '')
- assert_nil AutoId.find(test.id).value
- end
-
- def test_set_attributes_with_block
- topic = Topic.new do |t|
- t.title = "Budget"
- t.author_name = "Jason"
- end
-
- assert_equal("Budget", topic.title)
- assert_equal("Jason", topic.author_name)
- end
-
- def test_respond_to?
- topic = Topic.find(1)
- assert_respond_to topic, "title"
- assert_respond_to topic, "title?"
- assert_respond_to topic, "title="
- assert_respond_to topic, :title
- assert_respond_to topic, :title?
- assert_respond_to topic, :title=
- assert_respond_to topic, "author_name"
- assert_respond_to topic, "attribute_names"
- assert !topic.respond_to?("nothingness")
- assert !topic.respond_to?(:nothingness)
- end
-
- def test_array_content
- topic = Topic.new
- topic.content = %w( one two three )
- topic.save
-
- assert_equal(%w( one two three ), Topic.find(topic.id).content)
- end
-
- def test_read_attributes_before_type_cast
- category = Category.new({:name=>"Test categoty", :type => nil})
- category_attrs = {"name"=>"Test categoty", "type" => nil, "categorizations_count" => nil}
- assert_equal category_attrs , category.attributes_before_type_cast
- end
-
- if current_adapter?(:MysqlAdapter)
- def test_read_attributes_before_type_cast_on_boolean
- bool = Booleantest.create({ "value" => false })
- assert_equal "0", bool.reload.attributes_before_type_cast["value"]
- end
- end
-
- def test_read_attributes_before_type_cast_on_datetime
- developer = Developer.find(:first)
- # Oracle adapter returns Time before type cast
- unless current_adapter?(:OracleAdapter)
- assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"]
- else
- assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"].to_s(:db)
- end
- end
-
- def test_hash_content
- topic = Topic.new
- topic.content = { "one" => 1, "two" => 2 }
- topic.save
-
- assert_equal 2, Topic.find(topic.id).content["two"]
-
- topic.content_will_change!
- topic.content["three"] = 3
- topic.save
-
- assert_equal 3, Topic.find(topic.id).content["three"]
- end
-
- def test_update_array_content
- topic = Topic.new
- topic.content = %w( one two three )
-
- topic.content.push "four"
- assert_equal(%w( one two three four ), topic.content)
-
- topic.save
-
- topic = Topic.find(topic.id)
- topic.content << "five"
- assert_equal(%w( one two three four five ), topic.content)
- end
-
- def test_case_sensitive_attributes_hash
- # DB2 is not case-sensitive
- return true if current_adapter?(:DB2Adapter)
-
- assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.find(:first).attributes
- end
-
- def test_create
- topic = Topic.new
- topic.title = "New Topic"
- topic.save
- topic_reloaded = Topic.find(topic.id)
- assert_equal("New Topic", topic_reloaded.title)
- end
-
- def test_save!
- topic = Topic.new(:title => "New Topic")
- assert topic.save!
-
- reply = WrongReply.new
- assert_raise(ActiveRecord::RecordInvalid) { reply.save! }
- end
-
- def test_save_null_string_attributes
- topic = Topic.find(1)
- topic.attributes = { "title" => "null", "author_name" => "null" }
- topic.save!
- topic.reload
- assert_equal("null", topic.title)
- assert_equal("null", topic.author_name)
- end
-
- def test_save_nil_string_attributes
- topic = Topic.find(1)
- topic.title = nil
- topic.save!
- topic.reload
- assert_nil topic.title
- end
-
- def test_save_for_record_with_only_primary_key
- minimalistic = Minimalistic.new
- assert_nothing_raised { minimalistic.save }
- end
-
- def test_save_for_record_with_only_primary_key_that_is_provided
- assert_nothing_raised { Minimalistic.create!(:id => 2) }
- end
-
- def test_hashes_not_mangled
- new_topic = { :title => "New Topic" }
- new_topic_values = { :title => "AnotherTopic" }
-
- topic = Topic.new(new_topic)
- assert_equal new_topic[:title], topic.title
-
- topic.attributes= new_topic_values
- assert_equal new_topic_values[:title], topic.title
- end
-
- def test_create_many
- topics = Topic.create([ { "title" => "first" }, { "title" => "second" }])
- assert_equal 2, topics.size
- assert_equal "first", topics.first.title
- end
-
- def test_create_columns_not_equal_attributes
- topic = Topic.new
- topic.title = 'Another New Topic'
- topic.send :write_attribute, 'does_not_exist', 'test'
- assert_nothing_raised { topic.save }
- end
-
- def test_create_through_factory
- topic = Topic.create("title" => "New Topic")
- topicReloaded = Topic.find(topic.id)
- assert_equal(topic, topicReloaded)
- end
-
- def test_create_through_factory_with_block
- topic = Topic.create("title" => "New Topic") do |t|
- t.author_name = "David"
- end
- topicReloaded = Topic.find(topic.id)
- assert_equal("New Topic", topic.title)
- assert_equal("David", topic.author_name)
- end
-
- def test_create_many_through_factory_with_block
- topics = Topic.create([ { "title" => "first" }, { "title" => "second" }]) do |t|
- t.author_name = "David"
- end
- assert_equal 2, topics.size
- topic1, topic2 = Topic.find(topics[0].id), Topic.find(topics[1].id)
- assert_equal "first", topic1.title
- assert_equal "David", topic1.author_name
- assert_equal "second", topic2.title
- assert_equal "David", topic2.author_name
- end
-
- def test_update
- topic = Topic.new
- topic.title = "Another New Topic"
- topic.written_on = "2003-12-12 23:23:00"
- topic.save
- topicReloaded = Topic.find(topic.id)
- assert_equal("Another New Topic", topicReloaded.title)
-
- topicReloaded.title = "Updated topic"
- topicReloaded.save
-
- topicReloadedAgain = Topic.find(topic.id)
-
- assert_equal("Updated topic", topicReloadedAgain.title)
- end
-
- def test_update_columns_not_equal_attributes
- topic = Topic.new
- topic.title = "Still another topic"
- topic.save
-
- topicReloaded = Topic.find(topic.id)
- topicReloaded.title = "A New Topic"
- topicReloaded.send :write_attribute, 'does_not_exist', 'test'
- assert_nothing_raised { topicReloaded.save }
- end
-
- def test_update_for_record_with_only_primary_key
- minimalistic = minimalistics(:first)
- assert_nothing_raised { minimalistic.save }
- end
-
- def test_write_attribute
- topic = Topic.new
- topic.send(:write_attribute, :title, "Still another topic")
- assert_equal "Still another topic", topic.title
-
- topic.send(:write_attribute, "title", "Still another topic: part 2")
- assert_equal "Still another topic: part 2", topic.title
- end
-
- def test_read_attribute
- topic = Topic.new
- topic.title = "Don't change the topic"
- assert_equal "Don't change the topic", topic.send(:read_attribute, "title")
- assert_equal "Don't change the topic", topic["title"]
-
- assert_equal "Don't change the topic", topic.send(:read_attribute, :title)
- assert_equal "Don't change the topic", topic[:title]
- end
-
- def test_read_attribute_when_false
- topic = topics(:first)
- topic.approved = false
- assert !topic.approved?, "approved should be false"
- topic.approved = "false"
- assert !topic.approved?, "approved should be false"
- end
-
- def test_read_attribute_when_true
- topic = topics(:first)
- topic.approved = true
- assert topic.approved?, "approved should be true"
- topic.approved = "true"
- assert topic.approved?, "approved should be true"
- end
-
- def test_read_write_boolean_attribute
- topic = Topic.new
- # puts ""
- # puts "New Topic"
- # puts topic.inspect
- topic.approved = "false"
- # puts "Expecting false"
- # puts topic.inspect
- assert !topic.approved?, "approved should be false"
- topic.approved = "false"
- # puts "Expecting false"
- # puts topic.inspect
- assert !topic.approved?, "approved should be false"
- topic.approved = "true"
- # puts "Expecting true"
- # puts topic.inspect
- assert topic.approved?, "approved should be true"
- topic.approved = "true"
- # puts "Expecting true"
- # puts topic.inspect
- assert topic.approved?, "approved should be true"
- # puts ""
- end
-
- def test_query_attribute_string
- [nil, "", " "].each do |value|
- assert_equal false, Topic.new(:author_name => value).author_name?
- end
-
- assert_equal true, Topic.new(:author_name => "Name").author_name?
- end
-
- def test_query_attribute_number
- [nil, 0, "0"].each do |value|
- assert_equal false, Developer.new(:salary => value).salary?
- end
-
- assert_equal true, Developer.new(:salary => 1).salary?
- assert_equal true, Developer.new(:salary => "1").salary?
- end
-
- def test_query_attribute_boolean
- [nil, "", false, "false", "f", 0].each do |value|
- assert_equal false, Topic.new(:approved => value).approved?
- end
-
- [true, "true", "1", 1].each do |value|
- assert_equal true, Topic.new(:approved => value).approved?
- end
- end
-
- def test_query_attribute_with_custom_fields
- object = Company.find_by_sql(<<-SQL).first
- SELECT c1.*, c2.ruby_type as string_value, c2.rating as int_value
- FROM companies c1, companies c2
- WHERE c1.firm_id = c2.id
- AND c1.id = 2
- SQL
-
- assert_equal "Firm", object.string_value
- assert object.string_value?
-
- object.string_value = " "
- assert !object.string_value?
-
- assert_equal 1, object.int_value.to_i
- assert object.int_value?
-
- object.int_value = "0"
- assert !object.int_value?
- end
-
-
- def test_non_attribute_access_and_assignment
- topic = Topic.new
- assert !topic.respond_to?("mumbo")
- assert_raise(NoMethodError) { topic.mumbo }
- assert_raise(NoMethodError) { topic.mumbo = 5 }
- end
-
def test_preserving_date_objects
if current_adapter?(:SybaseAdapter)
# Sybase ctlib does not (yet?) support the date type; use datetime instead.
@@ -499,29 +148,6 @@ class BasicsTest < ActiveRecord::TestCase
assert topic.instance_variable_get("@custom_approved")
end
- def test_delete
- topic = Topic.find(1)
- assert_equal topic, topic.delete, 'topic.delete did not return self'
- assert topic.frozen?, 'topic not frozen after delete'
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) }
- end
-
- def test_delete_doesnt_run_callbacks
- Topic.find(1).delete
- assert_not_nil Topic.find(2)
- end
-
- def test_destroy
- topic = Topic.find(1)
- assert_equal topic, topic.destroy, 'topic.destroy did not return self'
- assert topic.frozen?, 'topic not frozen after destroy'
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) }
- end
-
- def test_record_not_found_exception
- assert_raise(ActiveRecord::RecordNotFound) { topicReloaded = Topic.find(99999) }
- end
-
def test_initialize_with_attributes
topic = Topic.new({
"title" => "initialized from attributes", "written_on" => "2003-12-12 23:23"
@@ -657,129 +283,13 @@ class BasicsTest < ActiveRecord::TestCase
GUESSED_CLASSES.each(&:reset_table_name)
end
- def test_destroy_all
- conditions = "author_name = 'Mary'"
- topics_by_mary = Topic.all(:conditions => conditions, :order => 'id')
- assert ! topics_by_mary.empty?
-
- assert_difference('Topic.count', -topics_by_mary.size) do
- destroyed = Topic.destroy_all(conditions).sort_by(&:id)
- assert_equal topics_by_mary, destroyed
- assert destroyed.all? { |topic| topic.frozen? }, "destroyed topics should be frozen"
- end
- end
-
- def test_destroy_many
- clients = Client.find([2, 3], :order => 'id')
- assert_difference('Client.count', -2) do
- destroyed = Client.destroy([2, 3]).sort_by(&:id)
- assert_equal clients, destroyed
- assert destroyed.all? { |client| client.frozen? }, "destroyed clients should be frozen"
- end
- end
-
- def test_delete_many
- original_count = Topic.count
- Topic.delete(deleting = [1, 2])
- assert_equal original_count - deleting.size, Topic.count
- end
-
- def test_boolean_attributes
- assert ! Topic.find(1).approved?
- assert Topic.find(2).approved?
- end
-
- def test_update_all
- assert_equal Topic.count, Topic.update_all("content = 'bulk updated!'")
- assert_equal "bulk updated!", Topic.find(1).content
- assert_equal "bulk updated!", Topic.find(2).content
-
- assert_equal Topic.count, Topic.update_all(['content = ?', 'bulk updated again!'])
- assert_equal "bulk updated again!", Topic.find(1).content
- assert_equal "bulk updated again!", Topic.find(2).content
-
- assert_equal Topic.count, Topic.update_all(['content = ?', nil])
- assert_nil Topic.find(1).content
- end
-
- def test_update_all_with_hash
- assert_not_nil Topic.find(1).last_read
- assert_equal Topic.count, Topic.update_all(:content => 'bulk updated with hash!', :last_read => nil)
- assert_equal "bulk updated with hash!", Topic.find(1).content
- assert_equal "bulk updated with hash!", Topic.find(2).content
- assert_nil Topic.find(1).last_read
- assert_nil Topic.find(2).last_read
- end
-
- def test_update_all_with_non_standard_table_name
- assert_equal 1, WarehouseThing.update_all(['value = ?', 0], ['id = ?', 1])
- assert_equal 0, WarehouseThing.find(1).value
- end
-
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
def test_update_all_with_order_and_limit
assert_equal 1, Topic.update_all("content = 'bulk updated!'", nil, :limit => 1, :order => 'id DESC')
end
end
- # Oracle UPDATE does not support ORDER BY
- unless current_adapter?(:OracleAdapter)
- def test_update_all_ignores_order_without_limit_from_association
- author = authors(:david)
- assert_nothing_raised do
- assert_equal author.posts_with_comments_and_categories.length, author.posts_with_comments_and_categories.update_all([ "body = ?", "bulk update!" ])
- end
- end
-
- def test_update_all_with_order_and_limit_updates_subset_only
- author = authors(:david)
- assert_nothing_raised do
- assert_equal 1, author.posts_sorted_by_id_limited.size
- assert_equal 2, author.posts_sorted_by_id_limited.find(:all, :limit => 2).size
- assert_equal 1, author.posts_sorted_by_id_limited.update_all([ "body = ?", "bulk update!" ])
- assert_equal "bulk update!", posts(:welcome).body
- assert_not_equal "bulk update!", posts(:thinking).body
- end
- end
- end
-
- def test_update_many
- topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
- updated = Topic.update(topic_data.keys, topic_data.values)
-
- assert_equal 2, updated.size
- assert_equal "1 updated", Topic.find(1).content
- assert_equal "2 updated", Topic.find(2).content
- end
-
- def test_delete_all
- assert Topic.count > 0
-
- assert_equal Topic.count, Topic.delete_all
- end
-
- def test_update_by_condition
- Topic.update_all "content = 'bulk updated!'", ["approved = ?", true]
- assert_equal "Have a nice day", Topic.find(1).content
- assert_equal "bulk updated!", Topic.find(2).content
- end
-
- def test_attribute_present
- t = Topic.new
- t.title = "hello there!"
- t.written_on = Time.now
- assert t.attribute_present?("title")
- assert t.attribute_present?("written_on")
- assert !t.attribute_present?("content")
- end
-
- def test_attribute_keys_on_new_instance
- t = Topic.new
- assert_equal nil, t.title, "The topics table has a title column, so it should be nil"
- assert_raise(NoMethodError) { t.title2 }
- end
-
def test_null_fields
assert_nil Topic.find(1).parent_id
assert_nil Topic.create("title" => "Hey you").parent_id
@@ -863,120 +373,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ]
end
- def test_delete_new_record
- client = Client.new
- client.delete
- assert client.frozen?
- end
-
- def test_delete_record_with_associations
- client = Client.find(3)
- client.delete
- assert client.frozen?
- assert_kind_of Firm, client.firm
- assert_raise(ActiveSupport::FrozenObjectError) { client.name = "something else" }
- end
-
- def test_destroy_new_record
- client = Client.new
- client.destroy
- assert client.frozen?
- end
-
- def test_destroy_record_with_associations
- client = Client.find(3)
- client.destroy
- assert client.frozen?
- assert_kind_of Firm, client.firm
- assert_raise(ActiveSupport::FrozenObjectError) { client.name = "something else" }
- end
-
- def test_update_attribute
- assert !Topic.find(1).approved?
- Topic.find(1).update_attribute("approved", true)
- assert Topic.find(1).approved?
-
- Topic.find(1).update_attribute(:approved, false)
- assert !Topic.find(1).approved?
- end
-
- def test_update_attribute_with_one_changed_and_one_updated
- t = Topic.order('id').limit(1).first
- title, author_name = t.title, t.author_name
- t.author_name = 'John'
- t.update_attribute(:title, 'super_title')
- assert_equal 'John', t.author_name
- assert_equal 'super_title', t.title
- assert t.changed?, "topic should have changed"
- assert t.author_name_changed?, "author_name should have changed"
- assert !t.title_changed?, "title should not have changed"
- assert_nil t.title_change, 'title change should be nil'
- assert_equal ['author_name'], t.changed
-
- t.reload
- assert_equal 'David', t.author_name
- assert_equal 'super_title', t.title
- end
-
- def test_update_attribute_with_one_updated
- t = Topic.first
- title = t.title
- t.update_attribute(:title, 'super_title')
- assert_equal 'super_title', t.title
- assert !t.changed?, "topic should not have changed"
- assert !t.title_changed?, "title should not have changed"
- assert_nil t.title_change, 'title change should be nil'
-
- t.reload
- assert_equal 'super_title', t.title
- end
-
- def test_update_attribute_for_udpated_at_on
- developer = Developer.find(1)
- updated_at = developer.updated_at
- developer.update_attribute(:salary, 80001)
- assert_not_equal updated_at, developer.updated_at
- developer.reload
- assert_not_equal updated_at, developer.updated_at
- end
-
- def test_update_attributes
- topic = Topic.find(1)
- assert !topic.approved?
- assert_equal "The First Topic", topic.title
-
- topic.update_attributes("approved" => true, "title" => "The First Topic Updated")
- topic.reload
- assert topic.approved?
- assert_equal "The First Topic Updated", topic.title
-
- topic.update_attributes(:approved => false, :title => "The First Topic")
- topic.reload
- assert !topic.approved?
- assert_equal "The First Topic", topic.title
- end
-
- def test_update_attributes!
- Reply.validates_presence_of(:title)
- reply = Reply.find(2)
- assert_equal "The Second Topic of the day", reply.title
- assert_equal "Have a nice day", reply.content
-
- reply.update_attributes!("title" => "The Second Topic of the day updated", "content" => "Have a nice evening")
- reply.reload
- assert_equal "The Second Topic of the day updated", reply.title
- assert_equal "Have a nice evening", reply.content
-
- reply.update_attributes!(:title => "The Second Topic of the day", :content => "Have a nice day")
- reply.reload
- assert_equal "The Second Topic of the day", reply.title
- assert_equal "Have a nice day", reply.content
-
- assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(:title => nil, :content => "Have a nice evening") }
- ensure
- Reply.reset_callbacks(:validate)
- end
-
def test_readonly_attributes
assert_equal Set.new([ 'title' , 'comments_count' ]), ReadonlyTitlePost.readonly_attributes
@@ -1236,35 +632,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal false, Topic.find(1).new_record?
end
- def test_destroyed_returns_boolean
- developer = Developer.first
- assert_equal false, developer.destroyed?
- developer.destroy
- assert_equal true, developer.destroyed?
-
- developer = Developer.last
- assert_equal false, developer.destroyed?
- developer.delete
- assert_equal true, developer.destroyed?
- end
-
- def test_persisted_returns_boolean
- developer = Developer.new(:name => "Jose")
- assert_equal false, developer.persisted?
- developer.save!
- assert_equal true, developer.persisted?
-
- developer = Developer.first
- assert_equal true, developer.persisted?
- developer.destroy
- assert_equal false, developer.persisted?
-
- developer = Developer.last
- assert_equal true, developer.persisted?
- developer.delete
- assert_equal false, developer.persisted?
- end
-
def test_clone
topic = Topic.find(1)
cloned_topic = nil
@@ -1607,67 +974,6 @@ class BasicsTest < ActiveRecord::TestCase
end
end
- def test_class_level_destroy
- should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
- Topic.find(1).replies << should_be_destroyed_reply
-
- Topic.destroy(1)
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) }
- assert_raise(ActiveRecord::RecordNotFound) { Reply.find(should_be_destroyed_reply.id) }
- end
-
- def test_class_level_delete
- should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
- Topic.find(1).replies << should_be_destroyed_reply
-
- Topic.delete(1)
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) }
- assert_nothing_raised { Reply.find(should_be_destroyed_reply.id) }
- end
-
- def test_increment_attribute
- assert_equal 50, accounts(:signals37).credit_limit
- accounts(:signals37).increment! :credit_limit
- assert_equal 51, accounts(:signals37, :reload).credit_limit
-
- accounts(:signals37).increment(:credit_limit).increment!(:credit_limit)
- assert_equal 53, accounts(:signals37, :reload).credit_limit
- end
-
- def test_increment_nil_attribute
- assert_nil topics(:first).parent_id
- topics(:first).increment! :parent_id
- assert_equal 1, topics(:first).parent_id
- end
-
- def test_increment_attribute_by
- assert_equal 50, accounts(:signals37).credit_limit
- accounts(:signals37).increment! :credit_limit, 5
- assert_equal 55, accounts(:signals37, :reload).credit_limit
-
- accounts(:signals37).increment(:credit_limit, 1).increment!(:credit_limit, 3)
- assert_equal 59, accounts(:signals37, :reload).credit_limit
- end
-
- def test_decrement_attribute
- assert_equal 50, accounts(:signals37).credit_limit
-
- accounts(:signals37).decrement!(:credit_limit)
- assert_equal 49, accounts(:signals37, :reload).credit_limit
-
- accounts(:signals37).decrement(:credit_limit).decrement!(:credit_limit)
- assert_equal 47, accounts(:signals37, :reload).credit_limit
- end
-
- def test_decrement_attribute_by
- assert_equal 50, accounts(:signals37).credit_limit
- accounts(:signals37).decrement! :credit_limit, 5
- assert_equal 45, accounts(:signals37, :reload).credit_limit
-
- accounts(:signals37).decrement(:credit_limit, 1).decrement!(:credit_limit, 3)
- assert_equal 41, accounts(:signals37, :reload).credit_limit
- end
-
def test_toggle_attribute
assert !topics(:first).approved?
topics(:first).toggle!(:approved)
@@ -1794,28 +1100,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal res6, res7
end
- def test_clear_association_cache_stored
- firm = Firm.find(1)
- assert_kind_of Firm, firm
-
- firm.clear_association_cache
- assert_equal Firm.find(1).clients.collect{ |x| x.name }.sort, firm.clients.collect{ |x| x.name }.sort
- end
-
- def test_clear_association_cache_new_record
- firm = Firm.new
- client_stored = Client.find(3)
- client_new = Client.new
- client_new.name = "The Joneses"
- clients = [ client_stored, client_new ]
-
- firm.clients << clients
- assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set
-
- firm.clear_association_cache
- assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set
- end
-
def test_interpolate_sql
assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo@bar') }
assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo bar) baz') }
@@ -2015,134 +1299,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_no_queries { assert true }
end
- def test_to_xml
- xml = REXML::Document.new(topics(:first).to_xml(:indent => 0))
- bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema
- written_on_in_current_timezone = topics(:first).written_on.xmlschema
- last_read_in_current_timezone = topics(:first).last_read.xmlschema
-
- assert_equal "topic", xml.root.name
- assert_equal "The First Topic" , xml.elements["//title"].text
- assert_equal "David" , xml.elements["//author-name"].text
- assert_match "Have a nice day", xml.elements["//content"].text
-
- assert_equal "1", xml.elements["//id"].text
- assert_equal "integer" , xml.elements["//id"].attributes['type']
-
- assert_equal "1", xml.elements["//replies-count"].text
- assert_equal "integer" , xml.elements["//replies-count"].attributes['type']
-
- assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text
- assert_equal "datetime" , xml.elements["//written-on"].attributes['type']
-
- assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text
-
- assert_equal nil, xml.elements["//parent-id"].text
- assert_equal "integer", xml.elements["//parent-id"].attributes['type']
- assert_equal "true", xml.elements["//parent-id"].attributes['nil']
-
- if current_adapter?(:SybaseAdapter)
- assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text
- assert_equal "datetime" , xml.elements["//last-read"].attributes['type']
- else
- # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb)
- assert_equal "2004-04-15", xml.elements["//last-read"].text
- assert_equal "date" , xml.elements["//last-read"].attributes['type']
- end
-
- # Oracle and DB2 don't have true boolean or time-only fields
- unless current_adapter?(:OracleAdapter, :DB2Adapter)
- assert_equal "false", xml.elements["//approved"].text
- assert_equal "boolean" , xml.elements["//approved"].attributes['type']
-
- assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text
- assert_equal "datetime" , xml.elements["//bonus-time"].attributes['type']
- end
- end
-
- def test_to_xml_skipping_attributes
- xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :replies_count])
- assert_equal "<topic>", xml.first(7)
- assert !xml.include?(%(<title>The First Topic</title>))
- assert xml.include?(%(<author-name>David</author-name>))
-
- xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :author_name, :replies_count])
- assert !xml.include?(%(<title>The First Topic</title>))
- assert !xml.include?(%(<author-name>David</author-name>))
- end
-
- def test_to_xml_including_has_many_association
- xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count)
- assert_equal "<topic>", xml.first(7)
- assert xml.include?(%(<replies type="array"><reply>))
- assert xml.include?(%(<title>The Second Topic of the day</title>))
- end
-
- def test_array_to_xml_including_has_many_association
- xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :include => :replies)
- assert xml.include?(%(<replies type="array"><reply>))
- end
-
- def test_array_to_xml_including_methods
- xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :methods => [ :topic_id ])
- assert xml.include?(%(<topic-id type="integer">#{topics(:first).topic_id}</topic-id>)), xml
- assert xml.include?(%(<topic-id type="integer">#{topics(:second).topic_id}</topic-id>)), xml
- end
-
- def test_array_to_xml_including_has_one_association
- xml = [ companies(:first_firm), companies(:rails_core) ].to_xml(:indent => 0, :skip_instruct => true, :include => :account)
- assert xml.include?(companies(:first_firm).account.to_xml(:indent => 0, :skip_instruct => true))
- assert xml.include?(companies(:rails_core).account.to_xml(:indent => 0, :skip_instruct => true))
- end
-
- def test_array_to_xml_including_belongs_to_association
- xml = [ companies(:first_client), companies(:second_client), companies(:another_client) ].to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
- assert xml.include?(companies(:first_client).to_xml(:indent => 0, :skip_instruct => true))
- assert xml.include?(companies(:second_client).firm.to_xml(:indent => 0, :skip_instruct => true))
- assert xml.include?(companies(:another_client).firm.to_xml(:indent => 0, :skip_instruct => true))
- end
-
- def test_to_xml_including_belongs_to_association
- xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
- assert !xml.include?("<firm>")
-
- xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
- assert xml.include?("<firm>")
- end
-
- def test_to_xml_including_multiple_associations
- xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ])
- assert_equal "<firm>", xml.first(6)
- assert xml.include?(%(<account>))
- assert xml.include?(%(<clients type="array"><client>))
- end
-
- def test_to_xml_including_multiple_associations_with_options
- xml = companies(:first_firm).to_xml(
- :indent => 0, :skip_instruct => true,
- :include => { :clients => { :only => :name } }
- )
-
- assert_equal "<firm>", xml.first(6)
- assert xml.include?(%(<client><name>Summit</name></client>))
- assert xml.include?(%(<clients type="array"><client>))
- end
-
- def test_to_xml_including_methods
- xml = Company.new.to_xml(:methods => :arbitrary_method, :skip_instruct => true)
- assert_equal "<company>", xml.first(9)
- assert xml.include?(%(<arbitrary-method>I am Jack's profound disappointment</arbitrary-method>))
- end
-
- def test_to_xml_with_block
- value = "Rockin' the block"
- xml = Company.new.to_xml(:skip_instruct => true) do |xml|
- xml.tag! "arbitrary-element", value
- end
- assert_equal "<company>", xml.first(9)
- assert xml.include?(%(<arbitrary-element>#{value}</arbitrary-element>))
- end
-
def test_to_param_should_return_string
assert_kind_of String, Client.find(:first).to_param
end
@@ -2237,15 +1393,6 @@ class BasicsTest < ActiveRecord::TestCase
ActiveRecord::Base.logger = original_logger
end
- def test_create_with_custom_timestamps
- custom_datetime = 1.hour.ago.beginning_of_day
-
- %w(created_at created_on updated_at updated_on).each do |attribute|
- parrot = LiveParrot.create(:name => "colombian", attribute => custom_datetime)
- assert_equal custom_datetime, parrot[attribute]
- end
- end
-
def test_dup
assert !Minimalistic.new.freeze.dup.frozen?
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 2c9d23c80f..afef31396e 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -325,7 +325,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_from_option_with_specified_index
- if Edge.connection.adapter_name == 'MySQL'
+ if Edge.connection.adapter_name == 'MySQL' or Edge.connection.adapter_name == 'Mysql2'
assert_equal Edge.count(:all), Edge.count(:all, :from => 'edges USE INDEX(unique_edge_index)')
assert_equal Edge.count(:all, :conditions => 'sink_id < 5'),
Edge.count(:all, :from => 'edges USE INDEX(unique_edge_index)', :conditions => 'sink_id < 5')
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index b5767344cd..cc6a6b44f2 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -68,6 +68,40 @@ class ColumnDefinitionTest < ActiveRecord::TestCase
end
end
+ if current_adapter?(:Mysql2Adapter)
+ def test_should_set_default_for_mysql_binary_data_types
+ binary_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", "a", "binary(1)")
+ assert_equal "a", binary_column.default
+
+ varbinary_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", "a", "varbinary(1)")
+ assert_equal "a", varbinary_column.default
+ end
+
+ def test_should_not_set_default_for_blob_and_text_data_types
+ assert_raise ArgumentError do
+ ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", "a", "blob")
+ end
+
+ assert_raise ArgumentError do
+ ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", "Hello", "text")
+ end
+
+ text_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", nil, "text")
+ assert_equal nil, text_column.default
+
+ not_null_text_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", nil, "text", false)
+ assert_equal "", not_null_text_column.default
+ end
+
+ def test_has_default_should_return_false_for_blog_and_test_data_types
+ blob_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", nil, "blob")
+ assert !blob_column.has_default?
+
+ text_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", nil, "text")
+ assert !text_column.has_default?
+ end
+ end
+
if current_adapter?(:PostgreSQLAdapter)
def test_bigint_column_should_map_to_integer
bigint_column = ActiveRecord::ConnectionAdapters::PostgreSQLColumn.new('number', nil, "bigint")
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
new file mode 100644
index 0000000000..c535119972
--- /dev/null
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -0,0 +1,25 @@
+require "cases/helper"
+
+class ConnectionManagementTest < ActiveRecord::TestCase
+ def setup
+ @env = {}
+ @app = stub('App')
+ @management = ActiveRecord::ConnectionAdapters::ConnectionManagement.new(@app)
+
+ @connections_cleared = false
+ ActiveRecord::Base.stubs(:clear_active_connections!).with { @connections_cleared = true }
+ end
+
+ test "clears active connections after each call" do
+ @app.expects(:call).with(@env)
+ @management.call(@env)
+ assert @connections_cleared
+ end
+
+ test "doesn't clear active connections when running in a test case" do
+ @env['rack.test'] = true
+ @app.expects(:call).with(@env)
+ @management.call(@env)
+ assert !@connections_cleared
+ end
+end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index cc9b2a45f4..82b3c36ed2 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -1,25 +1,31 @@
require "cases/helper"
-class ConnectionManagementTest < ActiveRecord::TestCase
- def setup
- @env = {}
- @app = stub('App')
- @management = ActiveRecord::ConnectionAdapters::ConnectionManagement.new(@app)
-
- @connections_cleared = false
- ActiveRecord::Base.stubs(:clear_active_connections!).with { @connections_cleared = true }
- end
-
- test "clears active connections after each call" do
- @app.expects(:call).with(@env)
- @management.call(@env)
- assert @connections_cleared
- end
-
- test "doesn't clear active connections when running in a test case" do
- @env['rack.test'] = true
- @app.expects(:call).with(@env)
- @management.call(@env)
- assert !@connections_cleared
+module ActiveRecord
+ module ConnectionAdapters
+ class ConnectionPoolTest < ActiveRecord::TestCase
+ def test_clear_stale_cached_connections!
+ pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec
+
+ threads = [
+ Thread.new { pool.connection },
+ Thread.new { pool.connection }]
+
+ threads.map { |t| t.join }
+
+ pool.extend Module.new {
+ attr_accessor :checkins
+ def checkin conn
+ @checkins << conn
+ conn.object_id
+ end
+ }
+ pool.checkins = []
+
+ cleared_threads = pool.clear_stale_cached_connections!
+ assert((cleared_threads - threads.map { |x| x.object_id }).empty?,
+ "threads should have been removed")
+ assert_equal pool.checkins.length, threads.length
+ end
+ end
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index ef29422824..0e90128907 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -39,7 +39,7 @@ class DefaultTest < ActiveRecord::TestCase
end
end
-if current_adapter?(:MysqlAdapter)
+if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
class DefaultsTestWithoutTransactionalFixtures < ActiveRecord::TestCase
# ActiveRecord::Base#create! (and #save and other related methods) will
# open a new transaction. When in transactional fixtures mode, this will
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 860d330a7f..4f3e43d77d 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -10,7 +10,6 @@ require 'models/entrant'
require 'models/project'
require 'models/developer'
require 'models/customer'
-require 'models/job'
class DynamicFinderMatchTest < ActiveRecord::TestCase
def test_find_no_match
@@ -694,6 +693,14 @@ class FinderTest < ActiveRecord::TestCase
assert_equal [], Topic.find_all_by_title("The First Topic!!")
end
+ def test_find_all_by_one_attribute_which_is_a_symbol
+ topics = Topic.find_all_by_content("Have a nice day".to_sym)
+ assert_equal 2, topics.size
+ assert topics.include?(topics(:first))
+
+ assert_equal [], Topic.find_all_by_title("The First Topic!!")
+ end
+
def test_find_all_by_one_attribute_that_is_an_aggregate
balance = customers(:david).balance
assert_kind_of Money, balance
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 8008b86f81..93f8749255 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -36,7 +36,7 @@ class FixturesTest < ActiveRecord::TestCase
fixtures = nil
assert_nothing_raised { fixtures = create_fixtures(name) }
assert_kind_of(Fixtures, fixtures)
- fixtures.each { |name, fixture|
+ fixtures.each { |_name, fixture|
fixture.each { |key, value|
assert_match(MATCH_ATTRIBUTE_NAME, key)
}
@@ -229,9 +229,9 @@ if Account.connection.respond_to?(:reset_pk_sequence!)
def test_create_fixtures_resets_sequences_when_not_cached
@instances.each do |instance|
- max_id = create_fixtures(instance.class.table_name).inject(0) do |max_id, (name, fixture)|
+ max_id = create_fixtures(instance.class.table_name).inject(0) do |_max_id, (name, fixture)|
fixture_id = fixture['id'].to_i
- fixture_id > max_id ? fixture_id : max_id
+ fixture_id > _max_id ? fixture_id : _max_id
end
# Clone the last fixture to check that it gets the next greatest id.
diff --git a/activerecord/test/cases/i18n_test.rb b/activerecord/test/cases/i18n_test.rb
index ae4dcfb81e..3287626378 100644
--- a/activerecord/test/cases/i18n_test.rb
+++ b/activerecord/test/cases/i18n_test.rb
@@ -2,7 +2,7 @@ require "cases/helper"
require 'models/topic'
require 'models/reply'
-class ActiveRecordI18nTests < Test::Unit::TestCase
+class ActiveRecordI18nTests < ActiveRecord::TestCase
def setup
I18n.backend = I18n::Backend::Simple.new
diff --git a/activerecord/test/cases/invalid_date_test.rb b/activerecord/test/cases/invalid_date_test.rb
index 99af7d2986..2de50b224c 100644
--- a/activerecord/test/cases/invalid_date_test.rb
+++ b/activerecord/test/cases/invalid_date_test.rb
@@ -1,7 +1,7 @@
require 'cases/helper'
require 'models/topic'
-class InvalidDateTest < Test::Unit::TestCase
+class InvalidDateTest < ActiveRecord::TestCase
def test_assign_valid_dates
valid_dates = [[2007, 11, 30], [1993, 2, 28], [2008, 2, 29]]
diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb
index c275557da8..2bc746c0b8 100644
--- a/activerecord/test/cases/json_serialization_test.rb
+++ b/activerecord/test/cases/json_serialization_test.rb
@@ -201,4 +201,11 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase
}
assert_equal %({"1":{"author":{"name":"David"}}}), ActiveSupport::JSON.encode(authors_hash, :only => [1, :name])
end
+
+ def test_should_be_able_to_encode_relation
+ authors_relation = Author.where(:id => [@david.id, @mary.id])
+
+ json = ActiveSupport::JSON.encode authors_relation, :only => :name
+ assert_equal '[{"author":{"name":"David"}},{"author":{"name":"Mary"}}]', json
+ end
end
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 66874cdad1..e7126964cd 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -53,7 +53,8 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::StaleObjectError) { p2.destroy }
assert p1.destroy
- assert_equal true, p1.frozen?
+ assert p1.frozen?
+ assert p1.destroyed?
assert_raises(ActiveRecord::RecordNotFound) { Person.find(1) }
end
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index 4aeae1fe45..cbaaca764b 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -2,8 +2,9 @@ require "cases/helper"
require "models/developer"
require "active_support/log_subscriber/test_helper"
-class LogSubscriberTest < ActiveSupport::TestCase
+class LogSubscriberTest < ActiveRecord::TestCase
include ActiveSupport::LogSubscriber::TestHelper
+ include ActiveSupport::BufferedLogger::Severity
def setup
@old_logger = ActiveRecord::Base.logger
@@ -39,4 +40,25 @@ class LogSubscriberTest < ActiveSupport::TestCase
assert_match(/CACHE/, @logger.logged(:debug).last)
assert_match(/SELECT .*?FROM .?developers.?/i, @logger.logged(:debug).last)
end
+
+ def test_basic_query_doesnt_log_when_level_is_not_debug
+ @logger.level = INFO
+ Developer.all
+ wait
+ assert_equal 0, @logger.logged(:debug).size
+ end
+
+ def test_cached_queries_doesnt_log_when_level_is_not_debug
+ @logger.level = INFO
+ ActiveRecord::Base.cache do
+ Developer.all
+ Developer.all
+ end
+ wait
+ assert_equal 0, @logger.logged(:debug).size
+ end
+
+ def test_initializes_runtime
+ Thread.new { assert_equal 0, ActiveRecord::LogSubscriber.runtime }.join
+ end
end
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index 4e8ce1dac1..5256ab8d11 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -8,7 +8,6 @@ require 'models/author'
require 'models/developer'
require 'models/project'
require 'models/comment'
-require 'models/category'
class MethodScopingTest < ActiveRecord::TestCase
fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects
@@ -209,6 +208,13 @@ class MethodScopingTest < ActiveRecord::TestCase
end
end
+ def test_scope_for_create_only_uses_equal
+ table = VerySpecialComment.arel_table
+ relation = VerySpecialComment.scoped
+ relation.where_values << table[:id].not_eq(1)
+ assert_equal({:type => "VerySpecialComment"}, relation.send(:scope_for_create))
+ end
+
def test_scoped_create
new_comment = nil
@@ -543,4 +549,4 @@ class NestedScopingTest < ActiveRecord::TestCase
assert_equal 1, scoped_authors.size
assert_equal authors(:david).attributes, scoped_authors.first.attributes
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 2c3fc46831..0cf3979694 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -256,7 +256,7 @@ if ActiveRecord::Base.connection.supports_migrations?
def test_create_table_with_defaults
# MySQL doesn't allow defaults on TEXT or BLOB columns.
- mysql = current_adapter?(:MysqlAdapter)
+ mysql = current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter)
Person.connection.create_table :testings do |t|
t.column :one, :string, :default => "hello"
@@ -313,7 +313,7 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_equal 'integer', four.sql_type
assert_equal 'bigint', eight.sql_type
assert_equal 'integer', eleven.sql_type
- elsif current_adapter?(:MysqlAdapter)
+ elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
assert_match 'int(11)', default.sql_type
assert_match 'tinyint', one.sql_type
assert_match 'int', four.sql_type
@@ -581,7 +581,7 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_kind_of BigDecimal, bob.wealth
end
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
def test_unabstracted_database_dependent_types
Person.delete_all
@@ -621,7 +621,7 @@ if ActiveRecord::Base.connection.supports_migrations?
assert !Person.column_methods_hash.include?(:last_name)
end
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
def testing_table_for_positioning
Person.connection.create_table :testings, :id => false do |t|
t.column :first, :integer
@@ -1447,7 +1447,7 @@ if ActiveRecord::Base.connection.supports_migrations?
columns = Person.connection.columns(:binary_testings)
data_column = columns.detect { |c| c.name == "data" }
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
assert_equal '', data_column.default
else
assert_nil data_column.default
@@ -1748,7 +1748,7 @@ if ActiveRecord::Base.connection.supports_migrations?
end
def integer_column
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
'int(11)'
elsif current_adapter?(:OracleAdapter)
'NUMBER(38)'
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index dc85b395d3..c42dda2ccb 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -270,27 +270,27 @@ class NamedScopeTest < ActiveRecord::TestCase
assert Topic.base.many?
end
- def test_should_build_with_proxy_options
+ def test_should_build_on_top_of_named_scope
topic = Topic.approved.build({})
assert topic.approved
end
- def test_should_build_new_with_proxy_options
+ def test_should_build_new_on_top_of_named_scope
topic = Topic.approved.new
assert topic.approved
end
- def test_should_create_with_proxy_options
+ def test_should_create_on_top_of_named_scope
topic = Topic.approved.create({})
assert topic.approved
end
- def test_should_create_with_bang_with_proxy_options
+ def test_should_create_with_bang_on_top_of_named_scope
topic = Topic.approved.create!({})
assert topic.approved
end
- def test_should_build_with_proxy_options_chained
+ def test_should_build_on_top_of_chained_named_scopes
topic = Topic.approved.by_lifo.build({})
assert topic.approved
assert_equal 'lifo', topic.author_name
@@ -478,4 +478,10 @@ class DynamicScopeTest < ActiveRecord::TestCase
assert_equal Post.scoped_by_author_id(1).find(1), Post.find(1)
assert_equal Post.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, Post.find(:first, :conditions => { :author_id => 1, :title => "Welcome to the weblog"})
end
+
+ def test_dynamic_scope_should_create_methods_after_hitting_method_missing
+ assert Developer.methods.grep(/scoped_by_created_at/).blank?
+ Developer.scoped_by_created_at(nil)
+ assert Developer.methods.grep(/scoped_by_created_at/).present?
+ end
end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index c9ea0d8c40..df09bbd46a 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -59,6 +59,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
pirate.save!
assert_equal 1, pirate.birds_with_reject_all_blank.count
+ assert_equal 'Tweetie', pirate.birds_with_reject_all_blank.first.name
end
def test_should_raise_an_ArgumentError_for_non_existing_associations
@@ -74,7 +75,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
ship = pirate.create_ship(:name => 'Nights Dirty Lightning')
assert_no_difference('Ship.count') do
- pirate.update_attributes(:ship_attributes => { '_destroy' => true })
+ pirate.update_attributes(:ship_attributes => { '_destroy' => true, :id => ship.id })
end
end
@@ -100,7 +101,8 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
pirate.ship_attributes = { :name => 'Red Pearl', :_reject_me_if_new => true }
assert_no_difference('Ship.count') { pirate.save! }
- # pirate.reject_empty_ships_on_create returns false for saved records
+ # pirate.reject_empty_ships_on_create returns false for saved pirate records
+ # in the previous step note that pirate gets saved but ship fails
pirate.ship_attributes = { :name => 'Red Pearl', :_reject_me_if_new => true }
assert_difference('Ship.count') { pirate.save! }
end
@@ -266,6 +268,28 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
end
assert_equal 'Mayflower', @ship.reload.name
end
+
+ def test_should_update_existing_when_update_only_is_true_and_id_is_given
+ @ship.delete
+ @ship = @pirate.create_update_only_ship(:name => 'Nights Dirty Lightning')
+
+ assert_no_difference('Ship.count') do
+ @pirate.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower', :id => @ship.id })
+ end
+ assert_equal 'Mayflower', @ship.reload.name
+ end
+
+ def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is_marked_for_destruction
+ Pirate.accepts_nested_attributes_for :update_only_ship, :update_only => true, :allow_destroy => true
+ @ship.delete
+ @ship = @pirate.create_update_only_ship(:name => 'Nights Dirty Lightning')
+
+ assert_difference('Ship.count', -1) do
+ @pirate.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower', :id => @ship.id, :_destroy => true })
+ end
+ Pirate.accepts_nested_attributes_for :update_only_ship, :update_only => true, :allow_destroy => false
+ end
+
end
class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
@@ -411,6 +435,27 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
end
assert_equal 'Arr', @pirate.reload.catchphrase
end
+
+ def test_should_update_existing_when_update_only_is_true_and_id_is_given
+ @pirate.delete
+ @pirate = @ship.create_update_only_pirate(:catchphrase => 'Aye')
+
+ assert_no_difference('Pirate.count') do
+ @ship.update_attributes(:update_only_pirate_attributes => { :catchphrase => 'Arr', :id => @pirate.id })
+ end
+ assert_equal 'Arr', @pirate.reload.catchphrase
+ end
+
+ def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is_marked_for_destruction
+ Ship.accepts_nested_attributes_for :update_only_pirate, :update_only => true, :allow_destroy => true
+ @pirate.delete
+ @pirate = @ship.create_update_only_pirate(:catchphrase => 'Aye')
+
+ assert_difference('Pirate.count', -1) do
+ @ship.update_attributes(:update_only_pirate_attributes => { :catchphrase => 'Arr', :id => @pirate.id, :_destroy => true })
+ end
+ Ship.accepts_nested_attributes_for :update_only_pirate, :update_only => true, :allow_destroy => false
+ end
end
module NestedAttributesOnACollectionAssociationTests
@@ -811,7 +856,25 @@ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveR
@part = @ship.parts.create!(:name => "Mast")
@trinket = @part.trinkets.create!(:name => "Necklace")
end
-
+
+ test "if association is not loaded and association record is saved and then in memory record attributes should be saved" do
+ @ship.parts_attributes=[{:id => @part.id,:name =>'Deck'}]
+ assert_equal 1, @ship.parts.proxy_target.size
+ assert_equal 'Deck', @ship.parts[0].name
+ end
+
+ test "if association is not loaded and child doesn't change and I am saving a grandchild then in memory record should be used" do
+ @ship.parts_attributes=[{:id => @part.id,:trinkets_attributes =>[{:id => @trinket.id, :name => 'Ruby'}]}]
+ assert_equal 1, @ship.parts.proxy_target.size
+ assert_equal 'Mast', @ship.parts[0].name
+ assert_no_difference("@ship.parts[0].trinkets.proxy_target.size") do
+ @ship.parts[0].trinkets.proxy_target.size
+ end
+ assert_equal 'Ruby', @ship.parts[0].trinkets[0].name
+ @ship.save
+ assert_equal 'Ruby', @ship.parts[0].trinkets[0].name
+ end
+
test "when grandchild changed in memory, saving parent should save grandchild" do
@trinket.name = "changed"
@ship.save
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
new file mode 100644
index 0000000000..d7666b19f6
--- /dev/null
+++ b/activerecord/test/cases/persistence_test.rb
@@ -0,0 +1,470 @@
+require "cases/helper"
+require 'models/post'
+require 'models/author'
+require 'models/topic'
+require 'models/reply'
+require 'models/category'
+require 'models/company'
+require 'models/developer'
+require 'models/project'
+require 'models/minimalistic'
+require 'models/warehouse_thing'
+require 'models/parrot'
+require 'models/minivan'
+require 'models/loose_person'
+require 'rexml/document'
+require 'active_support/core_ext/exception'
+
+class PersistencesTest < ActiveRecord::TestCase
+
+ fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts, :minivans
+
+ # Oracle UPDATE does not support ORDER BY
+ unless current_adapter?(:OracleAdapter)
+ def test_update_all_ignores_order_without_limit_from_association
+ author = authors(:david)
+ assert_nothing_raised do
+ assert_equal author.posts_with_comments_and_categories.length, author.posts_with_comments_and_categories.update_all([ "body = ?", "bulk update!" ])
+ end
+ end
+
+ def test_update_all_with_order_and_limit_updates_subset_only
+ author = authors(:david)
+ assert_nothing_raised do
+ assert_equal 1, author.posts_sorted_by_id_limited.size
+ assert_equal 2, author.posts_sorted_by_id_limited.find(:all, :limit => 2).size
+ assert_equal 1, author.posts_sorted_by_id_limited.update_all([ "body = ?", "bulk update!" ])
+ assert_equal "bulk update!", posts(:welcome).body
+ assert_not_equal "bulk update!", posts(:thinking).body
+ end
+ end
+ end
+
+ def test_update_many
+ topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
+ updated = Topic.update(topic_data.keys, topic_data.values)
+
+ assert_equal 2, updated.size
+ assert_equal "1 updated", Topic.find(1).content
+ assert_equal "2 updated", Topic.find(2).content
+ end
+
+ def test_delete_all
+ assert Topic.count > 0
+
+ assert_equal Topic.count, Topic.delete_all
+ end
+
+ def test_update_by_condition
+ Topic.update_all "content = 'bulk updated!'", ["approved = ?", true]
+ assert_equal "Have a nice day", Topic.find(1).content
+ assert_equal "bulk updated!", Topic.find(2).content
+ end
+
+ def test_increment_attribute
+ assert_equal 50, accounts(:signals37).credit_limit
+ accounts(:signals37).increment! :credit_limit
+ assert_equal 51, accounts(:signals37, :reload).credit_limit
+
+ accounts(:signals37).increment(:credit_limit).increment!(:credit_limit)
+ assert_equal 53, accounts(:signals37, :reload).credit_limit
+ end
+
+ def test_increment_nil_attribute
+ assert_nil topics(:first).parent_id
+ topics(:first).increment! :parent_id
+ assert_equal 1, topics(:first).parent_id
+ end
+
+ def test_increment_attribute_by
+ assert_equal 50, accounts(:signals37).credit_limit
+ accounts(:signals37).increment! :credit_limit, 5
+ assert_equal 55, accounts(:signals37, :reload).credit_limit
+
+ accounts(:signals37).increment(:credit_limit, 1).increment!(:credit_limit, 3)
+ assert_equal 59, accounts(:signals37, :reload).credit_limit
+ end
+
+ def test_destroy_all
+ conditions = "author_name = 'Mary'"
+ topics_by_mary = Topic.all(:conditions => conditions, :order => 'id')
+ assert ! topics_by_mary.empty?
+
+ assert_difference('Topic.count', -topics_by_mary.size) do
+ destroyed = Topic.destroy_all(conditions).sort_by(&:id)
+ assert_equal topics_by_mary, destroyed
+ assert destroyed.all? { |topic| topic.frozen? }, "destroyed topics should be frozen"
+ end
+ end
+
+ def test_destroy_many
+ clients = Client.find([2, 3], :order => 'id')
+
+ assert_difference('Client.count', -2) do
+ destroyed = Client.destroy([2, 3]).sort_by(&:id)
+ assert_equal clients, destroyed
+ assert destroyed.all? { |client| client.frozen? }, "destroyed clients should be frozen"
+ end
+ end
+
+ def test_delete_many
+ original_count = Topic.count
+ Topic.delete(deleting = [1, 2])
+ assert_equal original_count - deleting.size, Topic.count
+ end
+
+ def test_decrement_attribute
+ assert_equal 50, accounts(:signals37).credit_limit
+
+ accounts(:signals37).decrement!(:credit_limit)
+ assert_equal 49, accounts(:signals37, :reload).credit_limit
+
+ accounts(:signals37).decrement(:credit_limit).decrement!(:credit_limit)
+ assert_equal 47, accounts(:signals37, :reload).credit_limit
+ end
+
+ def test_decrement_attribute_by
+ assert_equal 50, accounts(:signals37).credit_limit
+ accounts(:signals37).decrement! :credit_limit, 5
+ assert_equal 45, accounts(:signals37, :reload).credit_limit
+
+ accounts(:signals37).decrement(:credit_limit, 1).decrement!(:credit_limit, 3)
+ assert_equal 41, accounts(:signals37, :reload).credit_limit
+ end
+
+ def test_create
+ topic = Topic.new
+ topic.title = "New Topic"
+ topic.save
+ topic_reloaded = Topic.find(topic.id)
+ assert_equal("New Topic", topic_reloaded.title)
+ end
+
+ def test_save!
+ topic = Topic.new(:title => "New Topic")
+ assert topic.save!
+
+ reply = WrongReply.new
+ assert_raise(ActiveRecord::RecordInvalid) { reply.save! }
+ end
+
+ def test_save_null_string_attributes
+ topic = Topic.find(1)
+ topic.attributes = { "title" => "null", "author_name" => "null" }
+ topic.save!
+ topic.reload
+ assert_equal("null", topic.title)
+ assert_equal("null", topic.author_name)
+ end
+
+ def test_save_nil_string_attributes
+ topic = Topic.find(1)
+ topic.title = nil
+ topic.save!
+ topic.reload
+ assert_nil topic.title
+ end
+
+ def test_save_for_record_with_only_primary_key
+ minimalistic = Minimalistic.new
+ assert_nothing_raised { minimalistic.save }
+ end
+
+ def test_save_for_record_with_only_primary_key_that_is_provided
+ assert_nothing_raised { Minimalistic.create!(:id => 2) }
+ end
+
+ def test_create_many
+ topics = Topic.create([ { "title" => "first" }, { "title" => "second" }])
+ assert_equal 2, topics.size
+ assert_equal "first", topics.first.title
+ end
+
+ def test_create_columns_not_equal_attributes
+ topic = Topic.new
+ topic.title = 'Another New Topic'
+ topic.send :write_attribute, 'does_not_exist', 'test'
+ assert_nothing_raised { topic.save }
+ end
+
+ def test_create_through_factory_with_block
+ topic = Topic.create("title" => "New Topic") do |t|
+ t.author_name = "David"
+ end
+ topicReloaded = Topic.find(topic.id)
+ assert_equal("New Topic", topic.title)
+ assert_equal("David", topic.author_name)
+ end
+
+ def test_create_many_through_factory_with_block
+ topics = Topic.create([ { "title" => "first" }, { "title" => "second" }]) do |t|
+ t.author_name = "David"
+ end
+ assert_equal 2, topics.size
+ topic1, topic2 = Topic.find(topics[0].id), Topic.find(topics[1].id)
+ assert_equal "first", topic1.title
+ assert_equal "David", topic1.author_name
+ assert_equal "second", topic2.title
+ assert_equal "David", topic2.author_name
+ end
+
+ def test_update
+ topic = Topic.new
+ topic.title = "Another New Topic"
+ topic.written_on = "2003-12-12 23:23:00"
+ topic.save
+ topicReloaded = Topic.find(topic.id)
+ assert_equal("Another New Topic", topicReloaded.title)
+
+ topicReloaded.title = "Updated topic"
+ topicReloaded.save
+
+ topicReloadedAgain = Topic.find(topic.id)
+
+ assert_equal("Updated topic", topicReloadedAgain.title)
+ end
+
+ def test_update_columns_not_equal_attributes
+ topic = Topic.new
+ topic.title = "Still another topic"
+ topic.save
+
+ topicReloaded = Topic.find(topic.id)
+ topicReloaded.title = "A New Topic"
+ topicReloaded.send :write_attribute, 'does_not_exist', 'test'
+ assert_nothing_raised { topicReloaded.save }
+ end
+
+ def test_update_for_record_with_only_primary_key
+ minimalistic = minimalistics(:first)
+ assert_nothing_raised { minimalistic.save }
+ end
+
+ def test_delete
+ topic = Topic.find(1)
+ assert_equal topic, topic.delete, 'topic.delete did not return self'
+ assert topic.frozen?, 'topic not frozen after delete'
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) }
+ end
+
+ def test_delete_doesnt_run_callbacks
+ Topic.find(1).delete
+ assert_not_nil Topic.find(2)
+ end
+
+ def test_destroy
+ topic = Topic.find(1)
+ assert_equal topic, topic.destroy, 'topic.destroy did not return self'
+ assert topic.frozen?, 'topic not frozen after destroy'
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) }
+ end
+
+ def test_record_not_found_exception
+ assert_raise(ActiveRecord::RecordNotFound) { topicReloaded = Topic.find(99999) }
+ end
+
+ def test_update_all
+ assert_equal Topic.count, Topic.update_all("content = 'bulk updated!'")
+ assert_equal "bulk updated!", Topic.find(1).content
+ assert_equal "bulk updated!", Topic.find(2).content
+
+ assert_equal Topic.count, Topic.update_all(['content = ?', 'bulk updated again!'])
+ assert_equal "bulk updated again!", Topic.find(1).content
+ assert_equal "bulk updated again!", Topic.find(2).content
+
+ assert_equal Topic.count, Topic.update_all(['content = ?', nil])
+ assert_nil Topic.find(1).content
+ end
+
+ def test_update_all_with_hash
+ assert_not_nil Topic.find(1).last_read
+ assert_equal Topic.count, Topic.update_all(:content => 'bulk updated with hash!', :last_read => nil)
+ assert_equal "bulk updated with hash!", Topic.find(1).content
+ assert_equal "bulk updated with hash!", Topic.find(2).content
+ assert_nil Topic.find(1).last_read
+ assert_nil Topic.find(2).last_read
+ end
+
+ def test_update_all_with_non_standard_table_name
+ assert_equal 1, WarehouseThing.update_all(['value = ?', 0], ['id = ?', 1])
+ assert_equal 0, WarehouseThing.find(1).value
+ end
+
+ def test_delete_new_record
+ client = Client.new
+ client.delete
+ assert client.frozen?
+ end
+
+ def test_delete_record_with_associations
+ client = Client.find(3)
+ client.delete
+ assert client.frozen?
+ assert_kind_of Firm, client.firm
+ assert_raise(ActiveSupport::FrozenObjectError) { client.name = "something else" }
+ end
+
+ def test_destroy_new_record
+ client = Client.new
+ client.destroy
+ assert client.frozen?
+ end
+
+ def test_destroy_record_with_associations
+ client = Client.find(3)
+ client.destroy
+ assert client.frozen?
+ assert_kind_of Firm, client.firm
+ assert_raise(ActiveSupport::FrozenObjectError) { client.name = "something else" }
+ end
+
+ def test_update_attribute
+ assert !Topic.find(1).approved?
+ Topic.find(1).update_attribute("approved", true)
+ assert Topic.find(1).approved?
+
+ Topic.find(1).update_attribute(:approved, false)
+ assert !Topic.find(1).approved?
+ end
+
+ def test_update_attribute_for_readonly_attribute
+ minivan = Minivan.find('m1')
+ assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_attribute(:color, 'black') }
+ end
+
+ def test_update_attribute_with_one_changed_and_one_updated
+ t = Topic.order('id').limit(1).first
+ title, author_name = t.title, t.author_name
+ t.author_name = 'John'
+ t.update_attribute(:title, 'super_title')
+ assert_equal 'John', t.author_name
+ assert_equal 'super_title', t.title
+ assert t.changed?, "topic should have changed"
+ assert t.author_name_changed?, "author_name should have changed"
+ assert !t.title_changed?, "title should not have changed"
+ assert_nil t.title_change, 'title change should be nil'
+ assert_equal ['author_name'], t.changed
+
+ t.reload
+ assert_equal 'David', t.author_name
+ assert_equal 'super_title', t.title
+ end
+
+ def test_update_attribute_with_one_updated
+ t = Topic.first
+ title = t.title
+ t.update_attribute(:title, 'super_title')
+ assert_equal 'super_title', t.title
+ assert !t.changed?, "topic should not have changed"
+ assert !t.title_changed?, "title should not have changed"
+ assert_nil t.title_change, 'title change should be nil'
+
+ t.reload
+ assert_equal 'super_title', t.title
+ end
+
+ def test_update_attribute_for_udpated_at_on
+ developer = Developer.find(1)
+ prev_month = Time.now.prev_month
+ developer.update_attribute(:updated_at, prev_month)
+ assert_equal prev_month, developer.updated_at
+ developer.update_attribute(:salary, 80001)
+ assert_not_equal prev_month, developer.updated_at
+ developer.reload
+ assert_not_equal prev_month, developer.updated_at
+ end
+
+ def test_update_attributes
+ topic = Topic.find(1)
+ assert !topic.approved?
+ assert_equal "The First Topic", topic.title
+
+ topic.update_attributes("approved" => true, "title" => "The First Topic Updated")
+ topic.reload
+ assert topic.approved?
+ assert_equal "The First Topic Updated", topic.title
+
+ topic.update_attributes(:approved => false, :title => "The First Topic")
+ topic.reload
+ assert !topic.approved?
+ assert_equal "The First Topic", topic.title
+ end
+
+ def test_update_attributes!
+ Reply.validates_presence_of(:title)
+ reply = Reply.find(2)
+ assert_equal "The Second Topic of the day", reply.title
+ assert_equal "Have a nice day", reply.content
+
+ reply.update_attributes!("title" => "The Second Topic of the day updated", "content" => "Have a nice evening")
+ reply.reload
+ assert_equal "The Second Topic of the day updated", reply.title
+ assert_equal "Have a nice evening", reply.content
+
+ reply.update_attributes!(:title => "The Second Topic of the day", :content => "Have a nice day")
+ reply.reload
+ assert_equal "The Second Topic of the day", reply.title
+ assert_equal "Have a nice day", reply.content
+
+ assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(:title => nil, :content => "Have a nice evening") }
+ ensure
+ Reply.reset_callbacks(:validate)
+ end
+
+ def test_destroyed_returns_boolean
+ developer = Developer.first
+ assert_equal false, developer.destroyed?
+ developer.destroy
+ assert_equal true, developer.destroyed?
+
+ developer = Developer.last
+ assert_equal false, developer.destroyed?
+ developer.delete
+ assert_equal true, developer.destroyed?
+ end
+
+ def test_persisted_returns_boolean
+ developer = Developer.new(:name => "Jose")
+ assert_equal false, developer.persisted?
+ developer.save!
+ assert_equal true, developer.persisted?
+
+ developer = Developer.first
+ assert_equal true, developer.persisted?
+ developer.destroy
+ assert_equal false, developer.persisted?
+
+ developer = Developer.last
+ assert_equal true, developer.persisted?
+ developer.delete
+ assert_equal false, developer.persisted?
+ end
+
+ def test_class_level_destroy
+ should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
+ Topic.find(1).replies << should_be_destroyed_reply
+
+ Topic.destroy(1)
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) }
+ assert_raise(ActiveRecord::RecordNotFound) { Reply.find(should_be_destroyed_reply.id) }
+ end
+
+ def test_class_level_delete
+ should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
+ Topic.find(1).replies << should_be_destroyed_reply
+
+ Topic.delete(1)
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) }
+ assert_nothing_raised { Reply.find(should_be_destroyed_reply.id) }
+ end
+
+ def test_create_with_custom_timestamps
+ custom_datetime = 1.hour.ago.beginning_of_day
+
+ %w(created_at created_on updated_at updated_on).each do |attribute|
+ parrot = LiveParrot.create(:name => "colombian", attribute => custom_datetime)
+ assert_equal custom_datetime, parrot[attribute]
+ end
+ end
+
+end
diff --git a/activerecord/test/cases/pk_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 73f4b3848c..1e44237e0a 100644
--- a/activerecord/test/cases/pk_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -13,7 +13,7 @@ class PrimaryKeysTest < ActiveRecord::TestCase
topic = Topic.new
assert topic.to_key.nil?
topic = Topic.find(1)
- assert_equal topic.to_key, [1]
+ assert_equal [1], topic.to_key
end
def test_to_key_with_customized_primary_key
@@ -26,7 +26,7 @@ class PrimaryKeysTest < ActiveRecord::TestCase
def test_to_key_with_primary_key_after_destroy
topic = Topic.find(1)
topic.destroy
- assert_equal topic.to_key, [1]
+ assert_equal [1], topic.to_key
end
def test_integer_key
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 68abca70b3..594db1d0ab 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -1,8 +1,6 @@
require "cases/helper"
require 'models/topic'
-require 'models/reply'
require 'models/task'
-require 'models/course'
require 'models/category'
require 'models/post'
@@ -59,7 +57,7 @@ class QueryCacheTest < ActiveRecord::TestCase
# Oracle adapter returns count() as Fixnum or Float
if current_adapter?(:OracleAdapter)
assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
- elsif current_adapter?(:SQLite3Adapter) && SQLite3::Version::VERSION > '1.2.5'
+ elsif current_adapter?(:SQLite3Adapter) && SQLite3::Version::VERSION > '1.2.5' or current_adapter?(:Mysql2Adapter)
# Future versions of the sqlite3 adapter will return numeric
assert_instance_of Fixnum,
Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index 41dcdbcd37..a50a4d4165 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -5,6 +5,8 @@ require 'models/developer'
require 'models/project'
require 'models/comment'
require 'models/category'
+require 'models/person'
+require 'models/reference'
class RelationScopingTest < ActiveRecord::TestCase
fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects
@@ -218,7 +220,7 @@ class NestedRelationScopingTest < ActiveRecord::TestCase
end
class HasManyScopingTest< ActiveRecord::TestCase
- fixtures :comments, :posts
+ fixtures :comments, :posts, :people, :references
def setup
@welcome = Post.find(1)
@@ -250,6 +252,23 @@ class HasManyScopingTest< ActiveRecord::TestCase
assert_equal 'a comment...', @welcome.comments.what_are_you
end
end
+
+ def test_should_maintain_default_scope_on_associations
+ person = people(:michael)
+ magician = BadReference.find(1)
+ assert_equal [magician], people(:michael).bad_references
+ end
+
+ def test_should_default_scope_on_associations_is_overriden_by_association_conditions
+ person = people(:michael)
+ assert_equal [], people(:michael).fixed_bad_references
+ end
+
+ def test_should_maintain_default_scope_on_eager_loaded_associations
+ michael = Person.where(:id => people(:michael).id).includes(:bad_references).first
+ magician = BadReference.find(1)
+ assert_equal [magician], michael.bad_references
+ end
end
class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase
@@ -364,6 +383,12 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal expected, received
end
+ def test_named_scope_reorders_default
+ expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.name }
+ received = DeveloperOrderedBySalary.reordered_by_name.find(:all).collect { |dev| dev.name }
+ assert_equal expected, received
+ end
+
def test_nested_exclusive_scope
expected = Developer.find(:all, :limit => 100).collect { |dev| dev.salary }
received = DeveloperOrderedBySalary.send(:with_exclusive_scope, :find => { :limit => 100 }) do
@@ -393,4 +418,4 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal nil, PoorDeveloperCalledJamis.create!(:salary => nil).salary
assert_equal 50000, PoorDeveloperCalledJamis.create!(:name => 'David').salary
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index ffde8daa07..ac7b501bb7 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1,5 +1,4 @@
require "cases/helper"
-require 'models/tag'
require 'models/tagging'
require 'models/post'
require 'models/topic'
@@ -23,6 +22,11 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 5, Post.where(:id => post_authors).size
end
+ def test_multivalue_where
+ posts = Post.where('author_id = ? AND id = ?', 1, 1)
+ assert_equal 1, posts.to_a.size
+ end
+
def test_scoped
topics = Topic.scoped
assert_kind_of ActiveRecord::Relation, topics
@@ -188,11 +192,23 @@ class RelationTest < ActiveRecord::TestCase
end
end
- def test_respond_to_private_arel_methods
+ def test_respond_to_delegates_to_relation
relation = Topic.scoped
+ fake_arel = Struct.new(:responds) {
+ def respond_to? method, access = false
+ responds << [method, access]
+ end
+ }.new []
+
+ relation.extend(Module.new { attr_accessor :arel })
+ relation.arel = fake_arel
+
+ relation.respond_to?(:matching_attributes)
+ assert_equal [:matching_attributes, false], fake_arel.responds.first
- assert ! relation.respond_to?(:matching_attributes)
- assert relation.respond_to?(:matching_attributes, true)
+ fake_arel.responds = []
+ relation.respond_to?(:matching_attributes, true)
+ assert_equal [:matching_attributes, true], fake_arel.responds.first
end
def test_respond_to_dynamic_finders
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 1c43e3c5b5..66446b6b7e 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -93,7 +93,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{c_int_4.*}, output
assert_no_match %r{c_int_4.*:limit}, output
- elsif current_adapter?(:MysqlAdapter)
+ elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
assert_match %r{c_int_1.*:limit => 1}, output
assert_match %r{c_int_2.*:limit => 2}, output
assert_match %r{c_int_3.*:limit => 3}, output
@@ -169,7 +169,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r(:primary_key => "movieid"), match[1], "non-standard primary key not preserved"
end
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
def test_schema_dump_should_not_add_default_value_for_mysql_text_field
output = standard_dump
assert_match %r{t.text\s+"body",\s+:null => false$}, output
diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb
index 8c385af97c..dab81530af 100644
--- a/activerecord/test/cases/serialization_test.rb
+++ b/activerecord/test/cases/serialization_test.rb
@@ -1,7 +1,13 @@
require "cases/helper"
require 'models/contact'
+require 'models/topic'
+require 'models/reply'
+require 'models/company'
class SerializationTest < ActiveRecord::TestCase
+
+ fixtures :topics, :companies, :accounts
+
FORMATS = [ :xml, :json ]
def setup
@@ -17,6 +23,134 @@ class SerializationTest < ActiveRecord::TestCase
@contact = Contact.new(@contact_attributes)
end
+ def test_to_xml
+ xml = REXML::Document.new(topics(:first).to_xml(:indent => 0))
+ bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema
+ written_on_in_current_timezone = topics(:first).written_on.xmlschema
+ last_read_in_current_timezone = topics(:first).last_read.xmlschema
+
+ assert_equal "topic", xml.root.name
+ assert_equal "The First Topic" , xml.elements["//title"].text
+ assert_equal "David" , xml.elements["//author-name"].text
+ assert_match "Have a nice day", xml.elements["//content"].text
+
+ assert_equal "1", xml.elements["//id"].text
+ assert_equal "integer" , xml.elements["//id"].attributes['type']
+
+ assert_equal "1", xml.elements["//replies-count"].text
+ assert_equal "integer" , xml.elements["//replies-count"].attributes['type']
+
+ assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text
+ assert_equal "datetime" , xml.elements["//written-on"].attributes['type']
+
+ assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text
+
+ assert_equal nil, xml.elements["//parent-id"].text
+ assert_equal "integer", xml.elements["//parent-id"].attributes['type']
+ assert_equal "true", xml.elements["//parent-id"].attributes['nil']
+
+ if current_adapter?(:SybaseAdapter)
+ assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text
+ assert_equal "datetime" , xml.elements["//last-read"].attributes['type']
+ else
+ # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb)
+ assert_equal "2004-04-15", xml.elements["//last-read"].text
+ assert_equal "date" , xml.elements["//last-read"].attributes['type']
+ end
+
+ # Oracle and DB2 don't have true boolean or time-only fields
+ unless current_adapter?(:OracleAdapter, :DB2Adapter)
+ assert_equal "false", xml.elements["//approved"].text
+ assert_equal "boolean" , xml.elements["//approved"].attributes['type']
+
+ assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text
+ assert_equal "datetime" , xml.elements["//bonus-time"].attributes['type']
+ end
+ end
+
+ def test_to_xml_skipping_attributes
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :replies_count])
+ assert_equal "<topic>", xml.first(7)
+ assert !xml.include?(%(<title>The First Topic</title>))
+ assert xml.include?(%(<author-name>David</author-name>))
+
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :author_name, :replies_count])
+ assert !xml.include?(%(<title>The First Topic</title>))
+ assert !xml.include?(%(<author-name>David</author-name>))
+ end
+
+ def test_to_xml_including_has_many_association
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count)
+ assert_equal "<topic>", xml.first(7)
+ assert xml.include?(%(<replies type="array"><reply>))
+ assert xml.include?(%(<title>The Second Topic of the day</title>))
+ end
+
+ def test_array_to_xml_including_has_many_association
+ xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :include => :replies)
+ assert xml.include?(%(<replies type="array"><reply>))
+ end
+
+ def test_array_to_xml_including_methods
+ xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :methods => [ :topic_id ])
+ assert xml.include?(%(<topic-id type="integer">#{topics(:first).topic_id}</topic-id>)), xml
+ assert xml.include?(%(<topic-id type="integer">#{topics(:second).topic_id}</topic-id>)), xml
+ end
+
+ def test_array_to_xml_including_has_one_association
+ xml = [ companies(:first_firm), companies(:rails_core) ].to_xml(:indent => 0, :skip_instruct => true, :include => :account)
+ assert xml.include?(companies(:first_firm).account.to_xml(:indent => 0, :skip_instruct => true))
+ assert xml.include?(companies(:rails_core).account.to_xml(:indent => 0, :skip_instruct => true))
+ end
+
+ def test_array_to_xml_including_belongs_to_association
+ xml = [ companies(:first_client), companies(:second_client), companies(:another_client) ].to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
+ assert xml.include?(companies(:first_client).to_xml(:indent => 0, :skip_instruct => true))
+ assert xml.include?(companies(:second_client).firm.to_xml(:indent => 0, :skip_instruct => true))
+ assert xml.include?(companies(:another_client).firm.to_xml(:indent => 0, :skip_instruct => true))
+ end
+
+ def test_to_xml_including_belongs_to_association
+ xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
+ assert !xml.include?("<firm>")
+
+ xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
+ assert xml.include?("<firm>")
+ end
+
+ def test_to_xml_including_multiple_associations
+ xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ])
+ assert_equal "<firm>", xml.first(6)
+ assert xml.include?(%(<account>))
+ assert xml.include?(%(<clients type="array"><client>))
+ end
+
+ def test_to_xml_including_multiple_associations_with_options
+ xml = companies(:first_firm).to_xml(
+ :indent => 0, :skip_instruct => true,
+ :include => { :clients => { :only => :name } }
+ )
+
+ assert_equal "<firm>", xml.first(6)
+ assert xml.include?(%(<client><name>Summit</name></client>))
+ assert xml.include?(%(<clients type="array"><client>))
+ end
+
+ def test_to_xml_including_methods
+ xml = Company.new.to_xml(:methods => :arbitrary_method, :skip_instruct => true)
+ assert_equal "<company>", xml.first(9)
+ assert xml.include?(%(<arbitrary-method>I am Jack's profound disappointment</arbitrary-method>))
+ end
+
+ def test_to_xml_with_block
+ value = "Rockin' the block"
+ xml = Company.new.to_xml(:skip_instruct => true) do |_xml|
+ _xml.tag! "arbitrary-element", value
+ end
+ assert_equal "<company>", xml.first(9)
+ assert xml.include?(%(<arbitrary-element>#{value}</arbitrary-element>))
+ end
+
def test_serialize_should_be_reversible
for format in FORMATS
@serialized = Contact.new.send("to_#{format}")
diff --git a/activerecord/test/cases/session_store/session_test.rb b/activerecord/test/cases/session_store/session_test.rb
new file mode 100644
index 0000000000..6f1c170a0c
--- /dev/null
+++ b/activerecord/test/cases/session_store/session_test.rb
@@ -0,0 +1,68 @@
+require 'cases/helper'
+require 'action_dispatch'
+require 'active_record/session_store'
+
+module ActiveRecord
+ class SessionStore
+ class SessionTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ def setup
+ super
+ Session.drop_table! if Session.table_exists?
+ end
+
+ def test_data_column_name
+ # default column name is 'data'
+ assert_equal 'data', Session.data_column_name
+ end
+
+ def test_table_name
+ assert_equal 'sessions', Session.table_name
+ end
+
+ def test_create_table!
+ assert !Session.table_exists?
+ Session.create_table!
+ assert Session.table_exists?
+ Session.drop_table!
+ assert !Session.table_exists?
+ end
+
+ def test_find_by_sess_id_compat
+ klass = Class.new(Session) do
+ def self.session_id_column
+ 'sessid'
+ end
+ end
+ klass.create_table!
+
+ assert klass.columns_hash['sessid'], 'sessid column exists'
+ session = klass.new(:data => 'hello')
+ session.sessid = "100"
+ session.save!
+
+ found = klass.find_by_session_id("100")
+ assert_equal session, found
+ assert_equal session.sessid, found.session_id
+ ensure
+ klass.drop_table!
+ end
+
+ def test_find_by_session_id
+ Session.create_table!
+ session_id = "10"
+ s = Session.create!(:data => 'world', :session_id => session_id)
+ t = Session.find_by_session_id(session_id)
+ assert_equal s, t
+ assert_equal s.data, t.data
+ Session.drop_table!
+ end
+
+ def test_loaded?
+ s = Session.new
+ assert !s.loaded?, 'session is not loaded'
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/session_store/sql_bypass.rb b/activerecord/test/cases/session_store/sql_bypass.rb
new file mode 100644
index 0000000000..f0ba166465
--- /dev/null
+++ b/activerecord/test/cases/session_store/sql_bypass.rb
@@ -0,0 +1,56 @@
+require 'cases/helper'
+require 'action_dispatch'
+require 'active_record/session_store'
+
+module ActiveRecord
+ class SessionStore
+ class SqlBypassTest < ActiveRecord::TestCase
+ def setup
+ super
+ Session.drop_table! if Session.table_exists?
+ end
+
+ def test_create_table
+ assert !Session.table_exists?
+ SqlBypass.create_table!
+ assert Session.table_exists?
+ SqlBypass.drop_table!
+ assert !Session.table_exists?
+ end
+
+ def test_new_record?
+ s = SqlBypass.new :data => 'foo', :session_id => 10
+ assert s.new_record?, 'this is a new record!'
+ end
+
+ def test_not_loaded?
+ s = SqlBypass.new({})
+ assert !s.loaded?, 'it is not loaded'
+ end
+
+ def test_loaded?
+ s = SqlBypass.new :data => 'hello'
+ assert s.loaded?, 'it is loaded'
+ end
+
+ def test_save
+ SqlBypass.create_table! unless Session.table_exists?
+ session_id = 20
+ s = SqlBypass.new :data => 'hello', :session_id => session_id
+ s.save
+ t = SqlBypass.find_by_session_id session_id
+ assert_equal s.session_id, t.session_id
+ assert_equal s.data, t.data
+ end
+
+ def test_destroy
+ SqlBypass.create_table! unless Session.table_exists?
+ session_id = 20
+ s = SqlBypass.new :data => 'hello', :session_id => session_id
+ s.save
+ s.destroy
+ assert_nil SqlBypass.find_by_session_id session_id
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 549c4af6b1..401439994d 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -2,9 +2,10 @@ require 'cases/helper'
require 'models/developer'
require 'models/owner'
require 'models/pet'
+require 'models/toy'
class TimestampTest < ActiveRecord::TestCase
- fixtures :developers, :owners, :pets
+ fixtures :developers, :owners, :pets, :toys
def setup
@developer = Developer.first
@@ -25,16 +26,26 @@ class TimestampTest < ActiveRecord::TestCase
end
def test_touching_a_record_updates_its_timestamp
+ previous_salary = @developer.salary
+ @developer.salary = previous_salary + 10000
@developer.touch
assert_not_equal @previously_updated_at, @developer.updated_at
+ assert_equal previous_salary + 10000, @developer.salary
+ assert @developer.salary_changed?, 'developer salary should have changed'
+ assert @developer.changed?, 'developer should be marked as changed'
+ @developer.reload
+ assert_equal previous_salary, @developer.salary
end
def test_touching_a_different_attribute
previously_created_at = @developer.created_at
@developer.touch(:created_at)
+ assert !@developer.created_at_changed? , 'created_at should not be changed'
+ assert !@developer.changed?, 'record should not be changed'
assert_not_equal previously_created_at, @developer.created_at
+ assert_not_equal @previously_updated_at, @developer.updated_at
end
def test_saving_a_record_with_a_belongs_to_that_specifies_touching_the_parent_should_update_the_parent_updated_at
@@ -72,4 +83,21 @@ class TimestampTest < ActiveRecord::TestCase
ensure
Pet.belongs_to :owner, :touch => true
end
+
+ def test_touching_a_record_touches_parent_record_and_grandparent_record
+ Toy.belongs_to :pet, :touch => true
+ Pet.belongs_to :owner, :touch => true
+
+ toy = Toy.first
+ pet = toy.pet
+ owner = pet.owner
+
+ owner.update_attribute(:updated_at, (time = 3.days.ago))
+ toy.touch
+ owner.reload
+
+ assert_not_equal time, owner.updated_at
+ ensure
+ Toy.belongs_to :pet
+ end
end
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index df123c9de8..d72c4bf7c4 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -1,6 +1,5 @@
require "cases/helper"
require 'models/topic'
-require 'models/reply'
class TransactionCallbacksTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
@@ -246,3 +245,44 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
assert_equal [:after_rollback], @second.history
end
end
+
+
+class TransactionObserverCallbacksTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+ fixtures :topics
+
+ class TopicWithObserverAttached < ActiveRecord::Base
+ set_table_name :topics
+ def history
+ @history ||= []
+ end
+ end
+
+ class TopicWithObserverAttachedObserver < ActiveRecord::Observer
+ def after_commit(record)
+ record.history.push :"TopicWithObserverAttachedObserver#after_commit"
+ end
+
+ def after_rollback(record)
+ record.history.push :"TopicWithObserverAttachedObserver#after_rollback"
+ end
+ end
+
+ def test_after_commit_called
+ topic = TopicWithObserverAttached.new
+ topic.save!
+
+ assert topic.history, [:"TopicWithObserverAttachedObserver#after_commit"]
+ end
+
+ def test_after_rollback_called
+ topic = TopicWithObserverAttached.new
+
+ Topic.transaction do
+ topic.save!
+ raise ActiveRecord::Rollback
+ end
+
+ assert topic.history, [:"TopicWithObserverObserver#after_rollback"]
+ end
+end
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 958a4e4f94..9255190613 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -3,10 +3,12 @@ require 'models/topic'
require 'models/reply'
require 'models/developer'
require 'models/book'
+require 'models/author'
+require 'models/post'
class TransactionTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
- fixtures :topics, :developers
+ fixtures :topics, :developers, :authors, :posts
def setup
@first, @second = Topic.find(1, 2).sort_by { |t| t.id }
@@ -103,6 +105,25 @@ class TransactionTest < ActiveRecord::TestCase
end
end
+ def test_update_attributes_should_rollback_on_failure
+ author = Author.find(1)
+ posts_count = author.posts.size
+ assert posts_count > 0
+ status = author.update_attributes(:name => nil, :post_ids => [])
+ assert !status
+ assert_equal posts_count, author.posts(true).size
+ end
+
+ def test_update_attributes_should_rollback_on_failure!
+ author = Author.find(1)
+ posts_count = author.posts.size
+ assert posts_count > 0
+ assert_raise(ActiveRecord::RecordInvalid) do
+ author.update_attributes!(:name => nil, :post_ids => [])
+ end
+ assert_equal posts_count, author.posts(true).size
+ end
+
def test_cancellation_from_before_destroy_rollbacks_in_destroy
add_cancelling_before_destroy_with_db_side_effect_to_topic
begin
diff --git a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
index 454e42ed37..628029f8df 100644
--- a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -1,6 +1,5 @@
require "cases/helper"
require 'models/topic'
-require 'models/reply'
class I18nGenerateMessageValidationTest < ActiveRecord::TestCase
def setup
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index 3f1b0e333f..fd771ef4be 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -4,11 +4,6 @@ require 'models/topic'
require 'models/reply'
require 'models/person'
require 'models/developer'
-require 'models/warehouse_thing'
-require 'models/guid'
-require 'models/owner'
-require 'models/pet'
-require 'models/event'
require 'models/parrot'
require 'models/company'
diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb
index 751946ffc5..b11b340e94 100644
--- a/activerecord/test/cases/xml_serialization_test.rb
+++ b/activerecord/test/cases/xml_serialization_test.rb
@@ -2,7 +2,6 @@ require "cases/helper"
require 'models/contact'
require 'models/post'
require 'models/author'
-require 'models/tagging'
require 'models/comment'
require 'models/company_in_module'
diff --git a/activerecord/test/connections/native_mysql2/connection.rb b/activerecord/test/connections/native_mysql2/connection.rb
new file mode 100644
index 0000000000..c6f198b1ac
--- /dev/null
+++ b/activerecord/test/connections/native_mysql2/connection.rb
@@ -0,0 +1,25 @@
+print "Using native Mysql2\n"
+require_dependency 'models/course'
+require 'logger'
+
+ActiveRecord::Base.logger = Logger.new("debug.log")
+
+# GRANT ALL PRIVILEGES ON activerecord_unittest.* to 'rails'@'localhost';
+# GRANT ALL PRIVILEGES ON activerecord_unittest2.* to 'rails'@'localhost';
+
+ActiveRecord::Base.configurations = {
+ 'arunit' => {
+ :adapter => 'mysql2',
+ :username => 'rails',
+ :encoding => 'utf8',
+ :database => 'activerecord_unittest',
+ },
+ 'arunit2' => {
+ :adapter => 'mysql2',
+ :username => 'rails',
+ :database => 'activerecord_unittest2'
+ }
+}
+
+ActiveRecord::Base.establish_connection 'arunit'
+Course.establish_connection 'arunit2'
diff --git a/activerecord/test/fixtures/dashboards.yml b/activerecord/test/fixtures/dashboards.yml
new file mode 100644
index 0000000000..e75bf46e6c
--- /dev/null
+++ b/activerecord/test/fixtures/dashboards.yml
@@ -0,0 +1,3 @@
+cool_first:
+ dashboard_id: d1
+ name: my_dashboard \ No newline at end of file
diff --git a/activerecord/test/fixtures/minivans.yml b/activerecord/test/fixtures/minivans.yml
new file mode 100644
index 0000000000..f1224a4c1a
--- /dev/null
+++ b/activerecord/test/fixtures/minivans.yml
@@ -0,0 +1,5 @@
+cool_first:
+ minivan_id: m1
+ name: my_minivan
+ speedometer_id: s1
+ color: blue
diff --git a/activerecord/test/fixtures/speedometers.yml b/activerecord/test/fixtures/speedometers.yml
new file mode 100644
index 0000000000..6a471aba0a
--- /dev/null
+++ b/activerecord/test/fixtures/speedometers.yml
@@ -0,0 +1,4 @@
+cool_first:
+ speedometer_id: s1
+ name: my_speedometer
+ dashboard_id: d1 \ No newline at end of file
diff --git a/activerecord/test/fixtures/subscriptions.yml b/activerecord/test/fixtures/subscriptions.yml
index 371bfd3422..5a93c12193 100644
--- a/activerecord/test/fixtures/subscriptions.yml
+++ b/activerecord/test/fixtures/subscriptions.yml
@@ -9,4 +9,4 @@ webster_rfr:
alterself_awdr:
id: 3
subscriber_id: alterself
- book_id: 3 \ No newline at end of file
+ book_id: 1
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 655b45bf57..727978431c 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -108,6 +108,8 @@ class Author < ActiveRecord::Base
%w(twitter github)
end
+ validates_presence_of :name
+
private
def log_before_adding(object)
@post_log << "before_adding#{object.id || '<new>'}"
diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb
index cfd07abddc..1e030b4f59 100644
--- a/activerecord/test/models/book.rb
+++ b/activerecord/test/models/book.rb
@@ -1,4 +1,7 @@
class Book < ActiveRecord::Base
has_many :citations, :foreign_key => 'book1_id'
has_many :references, :through => :citations, :source => :reference_of, :uniq => true
+
+ has_many :subscriptions
+ has_many :subscribers, :through => :subscriptions
end
diff --git a/activerecord/test/models/company_in_module.rb b/activerecord/test/models/company_in_module.rb
index 83d71b6909..2c8c30efb4 100644
--- a/activerecord/test/models/company_in_module.rb
+++ b/activerecord/test/models/company_in_module.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/object/misc'
+require 'active_support/core_ext/object/with_options'
module MyApplication
module Business
diff --git a/activerecord/test/models/country.rb b/activerecord/test/models/country.rb
new file mode 100644
index 0000000000..15e3a1de0b
--- /dev/null
+++ b/activerecord/test/models/country.rb
@@ -0,0 +1,7 @@
+class Country < ActiveRecord::Base
+
+ set_primary_key :country_id
+
+ has_and_belongs_to_many :treaties
+
+end
diff --git a/activerecord/test/models/dashboard.rb b/activerecord/test/models/dashboard.rb
new file mode 100644
index 0000000000..a8a25834b1
--- /dev/null
+++ b/activerecord/test/models/dashboard.rb
@@ -0,0 +1,3 @@
+class Dashboard < ActiveRecord::Base
+ set_primary_key :dashboard_id
+end \ No newline at end of file
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index de68fd7f24..c61c583c1d 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -88,6 +88,7 @@ class DeveloperOrderedBySalary < ActiveRecord::Base
self.table_name = 'developers'
default_scope :order => 'salary DESC'
scope :by_name, :order => 'name DESC'
+ scope :reordered_by_name, reorder('name DESC')
def self.all_ordered_by_name
with_scope(:find => { :order => 'name DESC' }) do
diff --git a/activerecord/test/models/electron.rb b/activerecord/test/models/electron.rb
new file mode 100644
index 0000000000..35af9f679b
--- /dev/null
+++ b/activerecord/test/models/electron.rb
@@ -0,0 +1,3 @@
+class Electron < ActiveRecord::Base
+ belongs_to :molecule
+end
diff --git a/activerecord/test/models/liquid.rb b/activerecord/test/models/liquid.rb
new file mode 100644
index 0000000000..b96c054f6c
--- /dev/null
+++ b/activerecord/test/models/liquid.rb
@@ -0,0 +1,5 @@
+class Liquid < ActiveRecord::Base
+ set_table_name :liquid
+ has_many :molecules, :uniq => true
+end
+
diff --git a/activerecord/test/models/minivan.rb b/activerecord/test/models/minivan.rb
new file mode 100644
index 0000000000..602438d16f
--- /dev/null
+++ b/activerecord/test/models/minivan.rb
@@ -0,0 +1,9 @@
+class Minivan < ActiveRecord::Base
+ set_primary_key :minivan_id
+
+ belongs_to :speedometer
+ has_one :dashboard, :through => :speedometer
+
+ attr_readonly :color
+
+end
diff --git a/activerecord/test/models/molecule.rb b/activerecord/test/models/molecule.rb
new file mode 100644
index 0000000000..69325b8d29
--- /dev/null
+++ b/activerecord/test/models/molecule.rb
@@ -0,0 +1,4 @@
+class Molecule < ActiveRecord::Base
+ belongs_to :liquid
+ has_many :electrons
+end
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index 2a73b1ee01..951ec93c53 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -4,6 +4,8 @@ class Person < ActiveRecord::Base
has_many :posts_with_no_comments, :through => :readers, :source => :post, :include => :comments, :conditions => 'comments.id is null'
has_many :references
+ has_many :bad_references
+ has_many :fixed_bad_references, :conditions => { :favourite => true }, :class_name => 'BadReference'
has_many :jobs, :through => :references
has_one :favourite_reference, :class_name => 'Reference', :conditions => ['favourite=?', true]
has_many :posts_with_comments_sorted_by_comment_id, :through => :readers, :source => :post, :include => :comments, :order => 'comments.id'
diff --git a/activerecord/test/models/reference.rb b/activerecord/test/models/reference.rb
index 479e8b72c6..4a17c936f5 100644
--- a/activerecord/test/models/reference.rb
+++ b/activerecord/test/models/reference.rb
@@ -2,3 +2,8 @@ class Reference < ActiveRecord::Base
belongs_to :person
belongs_to :job
end
+
+class BadReference < ActiveRecord::Base
+ self.table_name ='references'
+ default_scope :conditions => {:favourite => false }
+end
diff --git a/activerecord/test/models/speedometer.rb b/activerecord/test/models/speedometer.rb
new file mode 100644
index 0000000000..94743eff8e
--- /dev/null
+++ b/activerecord/test/models/speedometer.rb
@@ -0,0 +1,4 @@
+class Speedometer < ActiveRecord::Base
+ set_primary_key :speedometer_id
+ belongs_to :dashboard
+end \ No newline at end of file
diff --git a/activerecord/test/models/treaty.rb b/activerecord/test/models/treaty.rb
new file mode 100644
index 0000000000..b46537f0d2
--- /dev/null
+++ b/activerecord/test/models/treaty.rb
@@ -0,0 +1,7 @@
+class Treaty < ActiveRecord::Base
+
+ set_primary_key :treaty_id
+
+ has_and_belongs_to_many :countries
+
+end
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
new file mode 100644
index 0000000000..c78d99f4af
--- /dev/null
+++ b/activerecord/test/schema/mysql2_specific_schema.rb
@@ -0,0 +1,24 @@
+ActiveRecord::Schema.define do
+ create_table :binary_fields, :force => true, :options => 'CHARACTER SET latin1' do |t|
+ t.binary :tiny_blob, :limit => 255
+ t.binary :normal_blob, :limit => 65535
+ t.binary :medium_blob, :limit => 16777215
+ t.binary :long_blob, :limit => 2147483647
+ t.text :tiny_text, :limit => 255
+ t.text :normal_text, :limit => 65535
+ t.text :medium_text, :limit => 16777215
+ t.text :long_text, :limit => 2147483647
+ end
+
+ ActiveRecord::Base.connection.execute <<-SQL
+DROP PROCEDURE IF EXISTS ten;
+SQL
+
+ ActiveRecord::Base.connection.execute <<-SQL
+CREATE PROCEDURE ten() SQL SECURITY INVOKER
+BEGIN
+ select 10;
+END
+SQL
+
+end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index bea351b95a..fc3810f82b 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -164,6 +164,11 @@ ActiveRecord::Schema.define do
t.string :address_country
t.string :gps_location
end
+
+ create_table :dashboards, :force => true, :id => false do |t|
+ t.string :dashboard_id
+ t.string :name
+ end
create_table :developers, :force => true do |t|
t.string :name
@@ -290,6 +295,13 @@ ActiveRecord::Schema.define do
t.boolean :favourite
t.integer :lock_version, :default => 0
end
+
+ create_table :minivans, :force => true, :id => false do |t|
+ t.string :minivan_id
+ t.string :name
+ t.string :speedometer_id
+ t.string :color
+ end
create_table :minimalistics, :force => true do |t|
end
@@ -386,6 +398,7 @@ ActiveRecord::Schema.define do
create_table :pets, :primary_key => :pet_id ,:force => true do |t|
t.string :name
t.integer :owner_id, :integer
+ t.timestamps
end
create_table :pirates, :force => true do |t|
@@ -452,6 +465,12 @@ ActiveRecord::Schema.define do
t.string :name
t.integer :ship_id
end
+
+ create_table :speedometers, :force => true, :id => false do |t|
+ t.string :speedometer_id
+ t.string :name
+ t.string :dashboard_id
+ end
create_table :sponsors, :force => true do |t|
t.integer :club_id
@@ -512,6 +531,7 @@ ActiveRecord::Schema.define do
create_table :toys, :primary_key => :toy_id ,:force => true do |t|
t.string :name
t.integer :pet_id, :integer
+ t.timestamps
end
create_table :traffic_lights, :force => true do |t|
@@ -583,6 +603,34 @@ ActiveRecord::Schema.define do
t.string :title
end
+ create_table :countries, :force => true, :id => false, :primary_key => 'country_id' do |t|
+ t.string :country_id
+ t.string :name
+ end
+ create_table :treaties, :force => true, :id => false, :primary_key => 'treaty_id' do |t|
+ t.string :treaty_id
+ t.string :name
+ end
+ create_table :countries_treaties, :force => true, :id => false do |t|
+ t.string :country_id, :null => false
+ t.string :treaty_id, :null => false
+ t.datetime :created_at
+ t.datetime :updated_at
+ end
+
+ create_table :liquid, :force => true do |t|
+ t.string :name
+ end
+ create_table :molecules, :force => true do |t|
+ t.integer :liquid_id
+ t.string :name
+ end
+ create_table :electrons, :force => true do |t|
+ t.integer :molecule_id
+ t.string :name
+ end
+
+
except 'SQLite' do
# fk_test_has_fk should be before fk_test_has_pk
create_table :fk_test_has_fk, :force => true do |t|