aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile2
-rw-r--r--actionmailer/CHANGELOG.md4
-rw-r--r--actionmailer/lib/action_mailer/base.rb22
-rw-r--r--actionmailer/test/base_test.rb7
-rw-r--r--actionmailer/test/fixtures/base_mailer/attachment_with_hash.html.erb0
-rw-r--r--actionmailer/test/fixtures/base_mailer/attachment_with_hash_default_encoding.html.erb0
-rw-r--r--actionmailer/test/fixtures/base_mailer/welcome_with_headers.html.erb0
-rw-r--r--actionmailer/test/fixtures/base_test/after_filter_mailer/welcome.html.erb0
-rw-r--r--actionmailer/test/fixtures/base_test/before_filter_mailer/welcome.html.erb0
-rw-r--r--actionmailer/test/fixtures/base_test/default_inline_attachment_mailer/welcome.html.erb0
-rw-r--r--actionmailer/test/fixtures/mail_delivery_test/delivery_mailer/welcome.html.erb0
-rw-r--r--actionmailer/test/fixtures/proc_mailer/welcome.html.erb0
-rw-r--r--actionpack/CHANGELOG.md10
-rw-r--r--actionpack/actionpack.gemspec2
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb42
-rw-r--r--actionpack/lib/action_dispatch/middleware/public_exceptions.rb18
-rw-r--r--actionpack/lib/action_dispatch/routing.rb7
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb2
-rw-r--r--actionpack/lib/action_view.rb1
-rw-r--r--actionpack/lib/action_view/asset_paths.rb8
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb2
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb2
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb4
-rw-r--r--actionpack/lib/action_view/template/resolver.rb105
-rw-r--r--actionpack/test/abstract/helper_test.rb7
-rw-r--r--actionpack/test/controller/render_test.rb29
-rw-r--r--actionpack/test/controller/routing_test.rb15
-rw-r--r--actionpack/test/dispatch/routing_test.rb4
-rw-r--r--actionpack/test/fixtures/public/foo/baz.css3
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb14
-rw-r--r--actionpack/test/template/lookup_context_test.rb8
-rw-r--r--actionpack/test/template/testing/null_resolver_test.rb2
-rw-r--r--activemodel/lib/active_model.rb1
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb9
-rw-r--r--activemodel/lib/active_model/configuration.rb134
-rw-r--r--activemodel/lib/active_model/mass_assignment_security.rb10
-rw-r--r--activemodel/lib/active_model/serializers/json.rb5
-rw-r--r--activemodel/lib/active_model/validations.rb3
-rw-r--r--activemodel/test/cases/attribute_methods_test.rb9
-rw-r--r--activemodel/test/cases/configuration_test.rb154
-rw-r--r--activemodel/test/cases/serializers/xml_serialization_test.rb2
-rw-r--r--activerecord/CHANGELOG.md97
-rw-r--r--activerecord/README.rdoc12
-rw-r--r--activerecord/lib/active_record.rb11
-rw-r--r--activerecord/lib/active_record/aggregations.rb261
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb30
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb15
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/join_helper.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb2
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb23
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb26
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb29
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb8
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb13
-rw-r--r--activerecord/lib/active_record/callbacks.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb12
-rw-r--r--activerecord/lib/active_record/connection_handling.rb4
-rw-r--r--activerecord/lib/active_record/core.rb139
-rw-r--r--activerecord/lib/active_record/counter_cache.rb200
-rw-r--r--activerecord/lib/active_record/dynamic_matchers.rb3
-rw-r--r--activerecord/lib/active_record/errors.rb3
-rw-r--r--activerecord/lib/active_record/explain.rb11
-rw-r--r--activerecord/lib/active_record/explain_subscriber.rb7
-rw-r--r--activerecord/lib/active_record/fixtures.rb2
-rw-r--r--activerecord/lib/active_record/inheritance.rb10
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb8
-rw-r--r--activerecord/lib/active_record/migration.rb4
-rw-r--r--activerecord/lib/active_record/model.rb103
-rw-r--r--activerecord/lib/active_record/model_schema.rb23
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb6
-rw-r--r--activerecord/lib/active_record/persistence.rb22
-rw-r--r--activerecord/lib/active_record/railtie.rb26
-rw-r--r--activerecord/lib/active_record/railties/databases.rake244
-rw-r--r--activerecord/lib/active_record/readonly_attributes.rb2
-rw-r--r--activerecord/lib/active_record/reflection.rb64
-rw-r--r--activerecord/lib/active_record/relation.rb2
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb51
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb133
-rw-r--r--activerecord/lib/active_record/sanitization.rb32
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb12
-rw-r--r--activerecord/lib/active_record/scoping/default.rb2
-rw-r--r--activerecord/lib/active_record/serialization.rb12
-rw-r--r--activerecord/lib/active_record/serializers/xml_serializer.rb5
-rw-r--r--activerecord/lib/active_record/store.rb16
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb109
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb110
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb81
-rw-r--r--activerecord/lib/active_record/tasks/sqlite_database_tasks.rb55
-rw-r--r--activerecord/lib/active_record/timestamp.rb8
-rw-r--r--activerecord/test/active_record/connection_adapters/fake_adapter.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql/reserved_word_test.rb12
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb15
-rw-r--r--activerecord/test/cases/aggregations_test.rb158
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb4
-rw-r--r--activerecord/test/cases/attribute_methods/read_test.rb1
-rw-r--r--activerecord/test/cases/base_test.rb48
-rw-r--r--activerecord/test/cases/calculations_test.rb75
-rw-r--r--activerecord/test/cases/column_definition_test.rb6
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb5
-rw-r--r--activerecord/test/cases/database_tasks_test.rb275
-rw-r--r--activerecord/test/cases/deprecated_dynamic_methods_test.rb79
-rw-r--r--activerecord/test/cases/dirty_test.rb12
-rw-r--r--activerecord/test/cases/explain_subscriber_test.rb10
-rw-r--r--activerecord/test/cases/finder_respond_to_test.rb5
-rw-r--r--activerecord/test/cases/finder_test.rb104
-rw-r--r--activerecord/test/cases/json_serialization_test.rb47
-rw-r--r--activerecord/test/cases/mass_assignment_security_test.rb59
-rw-r--r--activerecord/test/cases/mysql_rake_test.rb246
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb10
-rw-r--r--activerecord/test/cases/persistence_test.rb63
-rw-r--r--activerecord/test/cases/postgresql_rake_test.rb200
-rw-r--r--activerecord/test/cases/reflection_test.rb24
-rw-r--r--activerecord/test/cases/relations_test.rb27
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb39
-rw-r--r--activerecord/test/cases/sqlite_rake_test.rb170
-rw-r--r--activerecord/test/cases/store_test.rb6
-rw-r--r--activerecord/test/cases/timestamp_test.rb4
-rw-r--r--activerecord/test/cases/xml_serialization_test.rb73
-rw-r--r--activerecord/test/fixtures/reserved_words/distinct_select.yml (renamed from activerecord/test/fixtures/reserved_words/distincts_selects.yml)6
-rw-r--r--activerecord/test/models/contact.rb49
-rw-r--r--activerecord/test/models/customer.rb7
-rw-r--r--activerecord/test/models/developer.rb5
-rw-r--r--activerecord/test/models/member.rb5
-rw-r--r--activerecord/test/models/topic.rb2
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb10
-rw-r--r--activerecord/test/schema/schema.rb5
-rw-r--r--activesupport/CHANGELOG.md2
-rw-r--r--activesupport/activesupport.gemspec2
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb11
-rw-r--r--activesupport/lib/active_support/deprecation.rb4
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb3
-rw-r--r--activesupport/lib/active_support/log_subscriber.rb58
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb2
-rw-r--r--activesupport/lib/active_support/notifications.rb11
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb31
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb18
-rw-r--r--activesupport/lib/active_support/testing/isolation.rb39
-rw-r--r--activesupport/test/clean_backtrace_test.rb11
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb12
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb12
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb7
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb13
-rw-r--r--activesupport/test/inflector_test.rb10
-rw-r--r--activesupport/test/inflector_test_cases.rb2
-rw-r--r--activesupport/test/notifications_test.rb4
-rw-r--r--guides/source/active_record_querying.textile22
-rw-r--r--guides/source/active_support_core_extensions.textile87
-rw-r--r--guides/source/rails_on_rack.textile17
-rw-r--r--guides/source/routing.textile8
-rw-r--r--guides/source/upgrading_ruby_on_rails.textile2
-rw-r--r--railties/lib/rails/generators/base.rb1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml4
-rw-r--r--railties/lib/rails/generators/test_case.rb12
-rw-r--r--railties/railties.gemspec6
-rw-r--r--railties/test/application/configuration_test.rb39
-rw-r--r--railties/test/application/rack/logger_test.rb2
-rw-r--r--railties/test/application/rake/notes_test.rb4
-rw-r--r--railties/test/application/rake_test.rb12
-rw-r--r--railties/test/isolation/abstract_unit.rb22
-rw-r--r--railties/test/railties/generators_test.rb8
173 files changed, 3122 insertions, 2033 deletions
diff --git a/Gemfile b/Gemfile
index c727f73020..4e34d42fb6 100644
--- a/Gemfile
+++ b/Gemfile
@@ -8,7 +8,7 @@ else
gem 'arel'
end
-gem 'minitest', '~> 3.0.0'
+gem 'minitest', '~> 3.1.0'
gem 'mocha', '>= 0.11.2'
gem 'rack-test', github: "brynary/rack-test"
gem 'bcrypt-ruby', '~> 3.0.0'
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index e6021939ff..a822412090 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,3 +1,7 @@
+## Rails 4.0.0 (unreleased) ##
+
+* Raise an `ActionView::MissingTemplate` exception when no implicit template could be found. *Damien Mathieu*
+
## Rails 3.2.5 (Jun 1, 2012) ##
* No changes.
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 95c1f9289f..739f9a52a9 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -184,6 +184,16 @@ module ActionMailer #:nodoc:
# and the second being a <tt>application/pdf</tt> with a Base64 encoded copy of the file.pdf book
# with the filename +free_book.pdf+.
#
+ # If you need to send attachments with no content, you need to create an empty view for it,
+ # or add an empty body parameter like this:
+ #
+ # class ApplicationMailer < ActionMailer::Base
+ # def welcome(recipient)
+ # attachments['free_book.pdf'] = File.read('path/to/file.pdf')
+ # mail(:to => recipient, :subject => "New account information", :body => "")
+ # end
+ # end
+ #
# = Inline Attachments
#
# You can also specify that a file should be displayed inline with other HTML. This is useful
@@ -598,8 +608,10 @@ module ActionMailer #:nodoc:
# end
# end
#
- # Will look for all templates at "app/views/notifier" with name "welcome". However, those
- # can be customized:
+ # Will look for all templates at "app/views/notifier" with name "welcome".
+ # If no welcome template exists, it will raise an ActionView::MissingTemplate error.
+ #
+ # However, those can be customized:
#
# mail(:template_path => 'notifications', :template_name => 'another')
#
@@ -733,7 +745,11 @@ module ActionMailer #:nodoc:
def each_template(paths, name, &block) #:nodoc:
templates = lookup_context.find_all(name, Array(paths))
- templates.uniq { |t| t.formats }.each(&block)
+ if templates.empty?
+ raise ActionView::MissingTemplate.new([paths], name, [paths], false, 'mailer')
+ else
+ templates.uniq { |t| t.formats }.each(&block)
+ end
end
def create_parts_from_responses(m, responses) #:nodoc:
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index 1d747ed18a..1b2e39b3f7 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -433,6 +433,13 @@ class BaseTest < ActiveSupport::TestCase
assert_equal("TEXT Implicit Multipart", mail.text_part.body.decoded)
end
+ test "should raise if missing template in implicit render" do
+ assert_raises ActionView::MissingTemplate do
+ BaseMailer.implicit_different_template('missing_template').deliver
+ end
+ assert_equal(0, BaseMailer.deliveries.length)
+ end
+
test "you can specify a different template for explicit render" do
mail = BaseMailer.explicit_different_template('explicit_multipart_templates').deliver
assert_equal("HTML Explicit Multipart Templates", mail.html_part.body.decoded)
diff --git a/actionmailer/test/fixtures/base_mailer/attachment_with_hash.html.erb b/actionmailer/test/fixtures/base_mailer/attachment_with_hash.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/base_mailer/attachment_with_hash.html.erb
diff --git a/actionmailer/test/fixtures/base_mailer/attachment_with_hash_default_encoding.html.erb b/actionmailer/test/fixtures/base_mailer/attachment_with_hash_default_encoding.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/base_mailer/attachment_with_hash_default_encoding.html.erb
diff --git a/actionmailer/test/fixtures/base_mailer/welcome_with_headers.html.erb b/actionmailer/test/fixtures/base_mailer/welcome_with_headers.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/base_mailer/welcome_with_headers.html.erb
diff --git a/actionmailer/test/fixtures/base_test/after_filter_mailer/welcome.html.erb b/actionmailer/test/fixtures/base_test/after_filter_mailer/welcome.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/base_test/after_filter_mailer/welcome.html.erb
diff --git a/actionmailer/test/fixtures/base_test/before_filter_mailer/welcome.html.erb b/actionmailer/test/fixtures/base_test/before_filter_mailer/welcome.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/base_test/before_filter_mailer/welcome.html.erb
diff --git a/actionmailer/test/fixtures/base_test/default_inline_attachment_mailer/welcome.html.erb b/actionmailer/test/fixtures/base_test/default_inline_attachment_mailer/welcome.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/base_test/default_inline_attachment_mailer/welcome.html.erb
diff --git a/actionmailer/test/fixtures/mail_delivery_test/delivery_mailer/welcome.html.erb b/actionmailer/test/fixtures/mail_delivery_test/delivery_mailer/welcome.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/mail_delivery_test/delivery_mailer/welcome.html.erb
diff --git a/actionmailer/test/fixtures/proc_mailer/welcome.html.erb b/actionmailer/test/fixtures/proc_mailer/welcome.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/proc_mailer/welcome.html.erb
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 8c2d887cb3..345b5aa330 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,15 @@
## Rails 4.0.0 (unreleased) ##
+* Support unicode characters in routes. Route will be automatically escaped, so instead of manually escaping:
+
+ get Rack::Utils.escape('こんにちは') => 'home#index'
+
+ You just have to write the unicode route:
+
+ get 'こんにちは' => 'home#index'
+
+ *kennyj*
+
* Return proper format on exceptions. *Santiago Pastorino*
* Allow to use mounted_helpers (helpers for accessing mounted engines) in ActionView::TestCase. *Piotr Sarnacki*
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index ae26d6f9e5..938f3d6dc2 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -26,5 +26,5 @@ Gem::Specification.new do |s|
s.add_dependency('journey', '~> 1.0.1')
s.add_dependency('erubis', '~> 2.7.0')
- s.add_development_dependency('tzinfo', '~> 0.3.29')
+ s.add_development_dependency('tzinfo', '~> 0.3.33')
end
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 5ee4c044ea..0b895e7860 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -84,17 +84,29 @@ module ActionDispatch
LAST_MODIFIED = "Last-Modified".freeze
ETAG = "ETag".freeze
CACHE_CONTROL = "Cache-Control".freeze
+ SPESHUL_KEYS = %w[extras no-cache max-age public must-revalidate]
+
+ def cache_control_headers
+ cache_control = {}
+ if cc = self[CACHE_CONTROL]
+ cc.delete(' ').split(',').each do |segment|
+ directive, argument = segment.split('=', 2)
+ case directive
+ when *SPESHUL_KEYS
+ key = directive.tr('-', '_')
+ cache_control[key.to_sym] = argument || true
+ else
+ cache_control[:extras] ||= []
+ cache_control[:extras] << segment
+ end
+ end
+ end
+ cache_control
+ end
def prepare_cache_control!
- @cache_control = {}
+ @cache_control = cache_control_headers
@etag = self[ETAG]
-
- if cache_control = self[CACHE_CONTROL]
- cache_control.split(/,\s*/).each do |segment|
- first, last = segment.split("=")
- @cache_control[first.to_sym] = last || true
- end
- end
end
def handle_conditional_get!
@@ -110,14 +122,24 @@ module ActionDispatch
MUST_REVALIDATE = "must-revalidate".freeze
def set_conditional_cache_control!
- return if self[CACHE_CONTROL].present?
+ control = {}
+ cc_headers = cache_control_headers
+ if extras = cc_headers.delete(:extras)
+ @cache_control[:extras] ||= []
+ @cache_control[:extras] += extras
+ @cache_control[:extras].uniq!
+ end
- control = @cache_control
+ control.merge! cc_headers
+ control.merge! @cache_control
if control.empty?
headers[CACHE_CONTROL] = DEFAULT_CACHE_CONTROL
elsif control[:no_cache]
headers[CACHE_CONTROL] = NO_CACHE
+ if control[:extras]
+ headers[CACHE_CONTROL] += ", #{control[:extras].join(', ')}"
+ end
else
extras = control[:extras]
max_age = control[:max_age]
diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
index 204f3f7fb3..53bedaa40a 100644
--- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
@@ -1,5 +1,4 @@
module ActionDispatch
- # A simple Rack application that renders exceptions in the given public path.
class PublicExceptions
attr_accessor :public_path
@@ -12,26 +11,24 @@ module ActionDispatch
status = env["PATH_INFO"][1..-1]
request = ActionDispatch::Request.new(env)
content_type = request.formats.first
- format = content_type && "to_#{content_type.to_sym}"
body = { :status => status, :error => exception.message }
- render(status, body, :format => format, :content_type => content_type)
+ render(status, content_type, body)
end
private
- def render(status, body, options)
- format = options[:format]
-
+ def render(status, content_type, body)
+ format = content_type && "to_#{content_type.to_sym}"
if format && body.respond_to?(format)
- render_format(status, body.public_send(format), options)
+ render_format(status, content_type, body.public_send(format))
else
render_html(status)
end
end
- def render_format(status, body, options)
- [status, {'Content-Type' => "#{options[:content_type]}; charset=#{ActionDispatch::Response.default_charset}",
+ def render_format(status, content_type, body)
+ [status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
'Content-Length' => body.bytesize.to_s}, [body]]
end
@@ -41,8 +38,7 @@ module ActionDispatch
path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path))
if found || File.exist?(path)
- body = File.read(path)
- [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
+ render_format(status, 'text/html', File.read(path))
else
[404, { "X-Cascade" => "pass" }, []]
end
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 38a0270151..29090882a5 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -1,3 +1,4 @@
+# encoding: UTF-8
require 'active_support/core_ext/object/to_param'
require 'active_support/core_ext/regexp'
@@ -218,6 +219,12 @@ module ActionDispatch
#
# match "/stories" => redirect("/posts")
#
+ # == Unicode character routes
+ #
+ # You can specify unicode character routes in your router:
+ #
+ # match "こんにちは" => "welcome#index"
+ #
# == Routing to Rack Applications
#
# Instead of a String, like <tt>posts#index</tt>, which corresponds to the
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 25d099d83e..94242ad962 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1387,7 +1387,7 @@ module ActionDispatch
options[:as] = name_for_action(options[:as], action)
end
- mapping = Mapping.new(@set, @scope, path, options)
+ mapping = Mapping.new(@set, @scope, URI.parser.escape(path), options)
app, conditions, requirements, defaults, as, anchor = mapping.to_route
@set.add_route(app, conditions, requirements, defaults, as, anchor)
end
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 899b40c66a..03dfa110e3 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -37,6 +37,7 @@ module ActionView
autoload :PathSet
autoload :Template
+
autoload_under "renderer" do
autoload :Renderer
autoload :AbstractRenderer
diff --git a/actionpack/lib/action_view/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb
index 4ce41d51f1..81880d17ea 100644
--- a/actionpack/lib/action_view/asset_paths.rb
+++ b/actionpack/lib/action_view/asset_paths.rb
@@ -35,7 +35,13 @@ module ActionView
# Return the filesystem path for the source
def compute_source_path(source, dir, ext)
source = rewrite_extension(source, dir, ext) if ext
- File.join(config.assets_dir, dir, source)
+
+ sources = []
+ sources << config.assets_dir
+ sources << dir unless source[0] == ?/
+ sources << source
+
+ File.join(sources)
end
def is_uri?(path)
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
index 05d5f1870a..1a54ca2399 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
@@ -7,7 +7,7 @@ module ActionView
module Helpers
module AssetTagHelper
- class AssetIncludeTag
+ class AssetIncludeTag #:nodoc:
include TagHelper
attr_reader :config, :asset_paths
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
index 4292d29f60..e0dbfe62c6 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
@@ -6,7 +6,7 @@ module ActionView
module Helpers
module AssetTagHelper
- class JavascriptIncludeTag < AssetIncludeTag
+ class JavascriptIncludeTag < AssetIncludeTag #:nodoc:
def asset_name
'javascript'
end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
index 57b0627225..91318b2812 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
@@ -6,7 +6,7 @@ module ActionView
module Helpers
module AssetTagHelper
- class StylesheetIncludeTag < AssetIncludeTag
+ class StylesheetIncludeTag < AssetIncludeTag #:nodoc:
def asset_name
'stylesheet'
end
@@ -53,7 +53,7 @@ module ActionView
# If the +source+ filename has no extension, <tt>.css</tt> will be appended (except for explicit URIs).
# Full paths from the document root will be passed through.
# Used internally by +stylesheet_link_tag+ to build the stylesheet path.
- #
+ #
# stylesheet_path "style" # => /stylesheets/style.css
# stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
# stylesheet_path "/dir/style.css" # => /dir/style.css
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index fa2038f78d..2bb656fac9 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -2,12 +2,14 @@ require "pathname"
require "active_support/core_ext/class"
require "active_support/core_ext/class/attribute_accessors"
require "action_view/template"
+require "thread"
+require "mutex_m"
module ActionView
# = Action View Resolver
class Resolver
# Keeps all information about view path and builds virtual path.
- class Path < String
+ class Path
attr_reader :name, :prefix, :partial, :virtual
alias_method :partial?, :partial
@@ -19,8 +21,77 @@ module ActionView
end
def initialize(name, prefix, partial, virtual)
- @name, @prefix, @partial = name, prefix, partial
- super(virtual)
+ @name = name
+ @prefix = prefix
+ @partial = partial
+ @virtual = virtual
+ end
+
+ def to_str
+ @virtual
+ end
+ alias :to_s :to_str
+ end
+
+ # Threadsafe template cache
+ class Cache #:nodoc:
+ class CacheEntry
+ include Mutex_m
+
+ attr_accessor :templates
+ end
+
+ def initialize
+ @data = Hash.new { |h1,k1| h1[k1] = Hash.new { |h2,k2|
+ h2[k2] = Hash.new { |h3,k3| h3[k3] = Hash.new { |h4,k4| h4[k4] = {} } } } }
+ @mutex = Mutex.new
+ end
+
+ # Cache the templates returned by the block
+ def cache(key, name, prefix, partial, locals)
+ cache_entry = nil
+
+ # first obtain a lock on the main data structure to create the cache entry
+ @mutex.synchronize do
+ cache_entry = @data[key][name][prefix][partial][locals] ||= CacheEntry.new
+ end
+
+ # then to avoid a long lasting global lock, obtain a more granular lock
+ # on the CacheEntry itself
+ cache_entry.synchronize do
+ if Resolver.caching?
+ cache_entry.templates ||= yield
+ else
+ fresh_templates = yield
+
+ if templates_have_changed?(cache_entry.templates, fresh_templates)
+ cache_entry.templates = fresh_templates
+ else
+ cache_entry.templates ||= []
+ end
+ end
+ end
+ end
+
+ def clear
+ @mutex.synchronize do
+ @data.clear
+ end
+ end
+
+ private
+
+ def templates_have_changed?(cached_templates, fresh_templates)
+ # if either the old or new template list is empty, we don't need to (and can't)
+ # compare modification times, and instead just check whether the lists are different
+ if cached_templates.blank? || fresh_templates.blank?
+ return fresh_templates.blank? != cached_templates.blank?
+ end
+
+ cached_templates_max_updated_at = cached_templates.map(&:updated_at).max
+
+ # if a template has changed, it will be now be newer than all the cached templates
+ fresh_templates.any? { |t| t.updated_at > cached_templates_max_updated_at }
end
end
@@ -32,12 +103,11 @@ module ActionView
end
def initialize
- @cached = Hash.new { |h1,k1| h1[k1] = Hash.new { |h2,k2|
- h2[k2] = Hash.new { |h3,k3| h3[k3] = Hash.new { |h4,k4| h4[k4] = {} } } } }
+ @cache = Cache.new
end
def clear_cache
- @cached.clear
+ @cache.clear
end
# Normalizes the arguments and passes it on to find_template.
@@ -65,27 +135,18 @@ module ActionView
# Handles templates caching. If a key is given and caching is on
# always check the cache before hitting the resolver. Otherwise,
- # it always hits the resolver but check if the resolver is fresher
- # before returning it.
+ # it always hits the resolver but if the key is present, check if the
+ # resolver is fresher before returning it.
def cached(key, path_info, details, locals) #:nodoc:
name, prefix, partial = path_info
locals = locals.map { |x| x.to_s }.sort!
- if key && caching?
- @cached[key][name][prefix][partial][locals] ||= decorate(yield, path_info, details, locals)
- else
- fresh = decorate(yield, path_info, details, locals)
- return fresh unless key
-
- scope = @cached[key][name][prefix][partial]
- cache = scope[locals]
- mtime = cache && cache.map(&:updated_at).max
-
- if !mtime || fresh.empty? || fresh.any? { |t| t.updated_at > mtime }
- scope[locals] = fresh
- else
- cache
+ if key
+ @cache.cache(key, name, prefix, partial, locals) do
+ decorate(yield, path_info, details, locals)
end
+ else
+ decorate(yield, path_info, details, locals)
end
end
diff --git a/actionpack/test/abstract/helper_test.rb b/actionpack/test/abstract/helper_test.rb
index 9a7445de7b..e79008fa9d 100644
--- a/actionpack/test/abstract/helper_test.rb
+++ b/actionpack/test/abstract/helper_test.rb
@@ -69,7 +69,12 @@ module AbstractController
end
def test_declare_missing_helper
- assert_raise(MissingSourceFile) { AbstractHelpers.helper :missing }
+ begin
+ AbstractHelpers.helper :missing
+ flunk "should have raised an exception"
+ rescue LoadError => e
+ assert_equal "helpers/missing_helper.rb", e.path
+ end
end
def test_helpers_with_module_through_block
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 3d58c02338..6bebe7e1ed 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -116,6 +116,12 @@ class TestController < ActionController::Base
render :action => 'hello_world'
end
+ def conditional_hello_with_cache_control_headers
+ response.headers['Cache-Control'] = 'no-transform'
+ expires_now
+ render :action => 'hello_world'
+ end
+
def conditional_hello_with_bangs
render :action => 'hello_world'
end
@@ -724,6 +730,14 @@ class TestController < ActionController::Base
end
end
+class MetalTestController < ActionController::Metal
+ include ActionController::Rendering
+
+ def accessing_logger_in_template
+ render :inline => "<%= logger.class %>"
+ end
+end
+
class RenderTest < ActionController::TestCase
tests TestController
@@ -1512,6 +1526,12 @@ class ExpiresInRenderTest < ActionController::TestCase
assert_equal "no-cache", @response.headers["Cache-Control"]
end
+ def test_expires_now_with_cache_control_headers
+ get :conditional_hello_with_cache_control_headers
+ assert_match(/no-cache/, @response.headers["Cache-Control"])
+ assert_match(/no-transform/, @response.headers["Cache-Control"])
+ end
+
def test_date_header_when_expires_in
time = Time.mktime(2011,10,30)
Time.stubs(:now).returns(time)
@@ -1605,3 +1625,12 @@ class LastModifiedRenderTest < ActionController::TestCase
assert_response :success
end
end
+
+class MetalRenderTest < ActionController::TestCase
+ tests MetalTestController
+
+ def test_access_to_logger_in_view
+ get :accessing_logger_in_template
+ assert_equal "NilClass", @response.body
+ end
+end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 2f552c3a5a..6cc1370105 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -270,6 +270,16 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
end
+ def test_specific_controller_action_failure
+ @rs.draw do
+ mount lambda {} => "/foo"
+ end
+
+ assert_raises(ActionController::RoutingError) do
+ url_for(@rs, :controller => "omg", :action => "lol")
+ end
+ end
+
def test_default_setup
@rs.draw { get '/:controller(/:action(/:id))' }
assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/content"))
@@ -1750,6 +1760,7 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
get 'account(/:action)' => "account#subscription"
get 'pages/:page_id/:controller(/:action(/:id))'
get ':controller/ping', :action => 'ping'
+ get 'こんにちは/世界', :controller => 'news', :action => 'index'
match ':controller(/:action(/:id))(.:format)', :via => :all
root :to => "news#index"
}
@@ -1866,6 +1877,10 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
assert_equal({:controller => 'people', :action => 'create', :person => { :name => 'Josh'}}, params)
end
+ def test_unicode_path
+ assert_equal({:controller => 'news', :action => 'index'}, @routes.recognize_path(URI.parser.escape('こんにちは/世界'), :method => :get))
+ end
+
private
def sort_extras!(extras)
if extras.length == 2
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index fa4cb301eb..f15dd7214b 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -2472,7 +2472,7 @@ end
class TestUnicodePaths < ActionDispatch::IntegrationTest
Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
app.draw do
- get "/#{Rack::Utils.escape("ほげ")}" => lambda { |env|
+ get "/ほげ" => lambda { |env|
[200, { 'Content-Type' => 'text/plain' }, []]
}, :as => :unicode_path
end
@@ -2727,4 +2727,4 @@ class TestInvalidUrls < ActionDispatch::IntegrationTest
assert_response :bad_request
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/fixtures/public/foo/baz.css b/actionpack/test/fixtures/public/foo/baz.css
new file mode 100644
index 0000000000..b5173fbef2
--- /dev/null
+++ b/actionpack/test/fixtures/public/foo/baz.css
@@ -0,0 +1,3 @@
+body {
+background: #000;
+}
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index 7cc567c72d..bcc55189b9 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -1267,9 +1267,6 @@ class AssetTagHelperTest < ActionView::TestCase
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css'))
end
-
-
-
def test_caching_stylesheet_include_tag_when_caching_off
ENV["RAILS_ASSET_ID"] = ""
config.perform_caching = false
@@ -1298,6 +1295,17 @@ class AssetTagHelperTest < ActionView::TestCase
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css'))
end
+
+ def test_caching_stylesheet_include_tag_with_absolute_uri
+ ENV["RAILS_ASSET_ID"] = ""
+
+ assert_dom_equal(
+ %(<link href="/stylesheets/all.css" media="screen" rel="stylesheet" />),
+ stylesheet_link_tag("/foo/baz", :cache => true)
+ )
+
+ FileUtils.rm(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
+ end
end
class AssetTagHelperNonVhostTest < ActionView::TestCase
diff --git a/actionpack/test/template/lookup_context_test.rb b/actionpack/test/template/lookup_context_test.rb
index 96b14a0acd..ef9c5ce10c 100644
--- a/actionpack/test/template/lookup_context_test.rb
+++ b/actionpack/test/template/lookup_context_test.rb
@@ -169,7 +169,7 @@ class LookupContextTest < ActiveSupport::TestCase
assert_not_equal template, old_template
end
-
+
test "responds to #prefixes" do
assert_equal [], @lookup_context.prefixes
@lookup_context.prefixes = ["foo"]
@@ -180,7 +180,7 @@ end
class LookupContextWithFalseCaching < ActiveSupport::TestCase
def setup
@resolver = ActionView::FixtureResolver.new("test/_foo.erb" => ["Foo", Time.utc(2000)])
- @resolver.stubs(:caching?).returns(false)
+ ActionView::Resolver.stubs(:caching?).returns(false)
@lookup_context = ActionView::LookupContext.new(@resolver, {})
end
@@ -247,6 +247,6 @@ class TestMissingTemplate < ActiveSupport::TestCase
@lookup_context.view_paths.find("foo", "parent", true, details)
end
assert_match %r{Missing partial parent/foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message
- end
-
+ end
+
end
diff --git a/actionpack/test/template/testing/null_resolver_test.rb b/actionpack/test/template/testing/null_resolver_test.rb
index 535ad3ab14..55ec36e753 100644
--- a/actionpack/test/template/testing/null_resolver_test.rb
+++ b/actionpack/test/template/testing/null_resolver_test.rb
@@ -6,7 +6,7 @@ class NullResolverTest < ActiveSupport::TestCase
templates = resolver.find_all("path.erb", "arbitrary", false, {:locale => [], :formats => [:html], :handlers => []})
assert_equal 1, templates.size, "expected one template"
assert_equal "Template generated by Null Resolver", templates.first.source
- assert_equal "arbitrary/path.erb", templates.first.virtual_path
+ assert_equal "arbitrary/path.erb", templates.first.virtual_path.to_s
assert_equal [:html], templates.first.formats
end
end
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index ded1b752df..f6bacf5ec0 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -30,7 +30,6 @@ module ActiveModel
autoload :AttributeMethods
autoload :BlockValidator, 'active_model/validator'
autoload :Callbacks
- autoload :Configuration
autoload :Conversion
autoload :Dirty
autoload :EachValidator, 'active_model/validator'
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 671c743b77..f30d00b355 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -60,8 +60,8 @@ module ActiveModel
CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
included do
- extend ActiveModel::Configuration
- config_attribute :attribute_method_matchers
+ class_attribute :attribute_aliases, :attribute_method_matchers, instance_writer: false
+ self.attribute_aliases = {}
self.attribute_method_matchers = [ClassMethods::AttributeMethodMatcher.new]
end
@@ -199,6 +199,7 @@ module ActiveModel
# person.name_short? # => true
# person.nickname_short? # => true
def alias_attribute(new_name, old_name)
+ self.attribute_aliases = attribute_aliases.merge(new_name.to_s => old_name.to_s)
attribute_method_matchers.each do |matcher|
matcher_new = matcher.method_name(new_name).to_s
matcher_old = matcher.method_name(old_name).to_s
@@ -316,7 +317,7 @@ module ActiveModel
end
protected
- def instance_method_already_implemented?(method_name)
+ def instance_method_already_implemented?(method_name) #:nodoc:
generated_attribute_methods.method_defined?(method_name)
end
@@ -372,7 +373,7 @@ module ActiveModel
RUBY
end
- class AttributeMethodMatcher
+ class AttributeMethodMatcher #:nodoc:
attr_reader :prefix, :suffix, :method_missing_target
AttributeMethodMatch = Struct.new(:target, :attr_name, :method_name)
diff --git a/activemodel/lib/active_model/configuration.rb b/activemodel/lib/active_model/configuration.rb
deleted file mode 100644
index ba5a6a2075..0000000000
--- a/activemodel/lib/active_model/configuration.rb
+++ /dev/null
@@ -1,134 +0,0 @@
-require 'active_support/concern'
-require 'active_support/core_ext/class/attribute'
-require 'active_support/core_ext/class/attribute_accessors'
-
-module ActiveModel
- # This API is for Rails' internal use and is not currently considered 'public', so
- # it may change in the future without warning.
- #
- # It creates configuration attributes that can be inherited from a module down
- # to a class that includes the module. E.g.
- #
- # module MyModel
- # extend ActiveModel::Configuration
- # config_attribute :awesome
- # self.awesome = true
- # end
- #
- # class Post
- # include MyModel
- # end
- #
- # Post.awesome # => true
- #
- # Post.awesome = false
- # Post.awesome # => false
- # MyModel.awesome # => true
- #
- # We assume that the module will have a ClassMethods submodule containing methods
- # to be transferred to the including class' singleton class.
- #
- # Config options can also be defined directly on a class:
- #
- # class Post
- # extend ActiveModel::Configuration
- # config_attribute :awesome
- # end
- #
- # So this allows us to define a module that doesn't care about whether it is being
- # included in a class or a module:
- #
- # module Awesomeness
- # extend ActiveSupport::Concern
- #
- # included do
- # extend ActiveModel::Configuration
- # config_attribute :awesome
- # self.awesome = true
- # end
- # end
- #
- # class Post
- # include Awesomeness
- # end
- #
- # module AwesomeModel
- # include Awesomeness
- # end
- module Configuration #:nodoc:
- def config_attribute(name, options = {})
- klass = self.is_a?(Class) ? ClassAttribute : ModuleAttribute
- klass.new(self, name, options).define
- end
-
- class Attribute
- attr_reader :host, :name, :options
-
- def initialize(host, name, options)
- @host, @name, @options = host, name, options
- end
-
- def instance_writer?
- options.fetch(:instance_writer, false)
- end
- end
-
- class ClassAttribute < Attribute
- def define
- if options[:global]
- host.cattr_accessor name, :instance_writer => instance_writer?
- else
- host.class_attribute name, :instance_writer => instance_writer?
- end
- end
- end
-
- class ModuleAttribute < Attribute
- def class_methods
- @class_methods ||= begin
- if host.const_defined?(:ClassMethods, false)
- host.const_get(:ClassMethods)
- else
- host.const_set(:ClassMethods, Module.new)
- end
- end
- end
-
- def define
- host.singleton_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
- attr_accessor :#{name}
- def #{name}?; !!#{name}; end
- CODE
-
- name, host = self.name, self.host
-
- class_methods.class_eval do
- define_method(name) { host.send(name) }
- define_method("#{name}?") { !!send(name) }
- end
-
- host.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}; defined?(@#{name}) ? @#{name} : self.class.#{name}; end
- def #{name}?; !!#{name}; end
- CODE
-
- if options[:global]
- class_methods.class_eval do
- define_method("#{name}=") { |val| host.send("#{name}=", val) }
- end
- else
- class_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}=(val)
- singleton_class.class_eval do
- remove_possible_method(:#{name})
- define_method(:#{name}) { val }
- end
- end
- CODE
- end
-
- host.send(:attr_writer, name) if instance_writer?
- end
- end
- end
-end
diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb
index cfce1542b1..8f2c0bf00a 100644
--- a/activemodel/lib/active_model/mass_assignment_security.rb
+++ b/activemodel/lib/active_model/mass_assignment_security.rb
@@ -9,13 +9,11 @@ module ActiveModel
extend ActiveSupport::Concern
included do
- extend ActiveModel::Configuration
+ class_attribute :_accessible_attributes, instance_writer: false
+ class_attribute :_protected_attributes, instance_writer: false
+ class_attribute :_active_authorizer, instance_writer: false
- config_attribute :_accessible_attributes
- config_attribute :_protected_attributes
- config_attribute :_active_authorizer
-
- config_attribute :_mass_assignment_sanitizer
+ class_attribute :_mass_assignment_sanitizer, instance_writer: false
self.mass_assignment_sanitizer = :logger
end
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index e668b4a009..b4baf3a946 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -10,9 +10,8 @@ module ActiveModel
included do
extend ActiveModel::Naming
- extend ActiveModel::Configuration
- config_attribute :include_root_in_json
+ class_attribute :include_root_in_json
self.include_root_in_json = false
end
@@ -106,4 +105,4 @@ module ActiveModel
end
end
end
-end \ No newline at end of file
+end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 7b980d43e6..cd596e37d2 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -51,8 +51,7 @@ module ActiveModel
attr_accessor :validation_context
define_callbacks :validate, :scope => :name
- extend ActiveModel::Configuration
- config_attribute :_validators
+ class_attribute :_validators
self._validators = Hash.new { |h,k| h[k] = [] }
end
diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb
index e2f2cecc09..baaf842222 100644
--- a/activemodel/test/cases/attribute_methods_test.rb
+++ b/activemodel/test/cases/attribute_methods_test.rb
@@ -154,6 +154,15 @@ class AttributeMethodsTest < ActiveModel::TestCase
assert_equal "value of foo", ModelWithAttributes.new.foo
end
+ test '#alias_attribute generates attribute_aliases lookup hash' do
+ klass = Class.new(ModelWithAttributes) do
+ define_attribute_methods :foo
+ alias_attribute :bar, :foo
+ end
+
+ assert_equal({ "bar" => "foo" }, klass.attribute_aliases)
+ end
+
test '#define_attribute_methods generates attribute methods with spaces in their names' do
ModelWithAttributesWithSpaces.define_attribute_methods(:'foo bar')
diff --git a/activemodel/test/cases/configuration_test.rb b/activemodel/test/cases/configuration_test.rb
deleted file mode 100644
index a172fa26a3..0000000000
--- a/activemodel/test/cases/configuration_test.rb
+++ /dev/null
@@ -1,154 +0,0 @@
-require 'cases/helper'
-
-class ConfigurationOnModuleTest < ActiveModel::TestCase
- def setup
- @mod = mod = Module.new do
- extend ActiveSupport::Concern
- extend ActiveModel::Configuration
-
- config_attribute :omg
- self.omg = "default"
-
- config_attribute :wtf, global: true
- self.wtf = "default"
-
- config_attribute :boolean
-
- config_attribute :lol, instance_writer: true
- end
-
- @klass = Class.new do
- include mod
- end
-
- @subklass = Class.new(@klass)
- end
-
- test "default" do
- assert_equal "default", @mod.omg
- assert_equal "default", @klass.omg
- assert_equal "default", @klass.new.omg
- end
-
- test "setting" do
- @mod.omg = "lol"
- assert_equal "lol", @mod.omg
- end
-
- test "setting on class including the module" do
- @klass.omg = "lol"
- assert_equal "lol", @klass.omg
- assert_equal "lol", @klass.new.omg
- assert_equal "default", @mod.omg
- end
-
- test "setting on subclass of class including the module" do
- @subklass.omg = "lol"
- assert_equal "lol", @subklass.omg
- assert_equal "default", @klass.omg
- assert_equal "default", @mod.omg
- end
-
- test "setting on instance" do
- assert !@klass.new.respond_to?(:omg=)
-
- @klass.lol = "lol"
- obj = @klass.new
- assert_equal "lol", obj.lol
- obj.lol = "omg"
- assert_equal "omg", obj.lol
- assert_equal "lol", @klass.lol
- assert_equal "lol", @klass.new.lol
- obj.lol = false
- assert !obj.lol?
- end
-
- test "global attribute" do
- assert_equal "default", @mod.wtf
- assert_equal "default", @klass.wtf
-
- @mod.wtf = "wtf"
-
- assert_equal "wtf", @mod.wtf
- assert_equal "wtf", @klass.wtf
-
- @klass.wtf = "lol"
-
- assert_equal "lol", @mod.wtf
- assert_equal "lol", @klass.wtf
- end
-
- test "boolean" do
- assert_equal false, @mod.boolean?
- assert_equal false, @klass.new.boolean?
- @mod.boolean = true
- assert_equal true, @mod.boolean?
- assert_equal true, @klass.new.boolean?
- end
-end
-
-class ConfigurationOnClassTest < ActiveModel::TestCase
- def setup
- @klass = Class.new do
- extend ActiveModel::Configuration
-
- config_attribute :omg
- self.omg = "default"
-
- config_attribute :wtf, global: true
- self.wtf = "default"
-
- config_attribute :omg2, instance_writer: true
- config_attribute :wtf2, instance_writer: true, global: true
- end
-
- @subklass = Class.new(@klass)
- end
-
- test "defaults" do
- assert_equal "default", @klass.omg
- assert_equal "default", @klass.wtf
- assert_equal "default", @subklass.omg
- assert_equal "default", @subklass.wtf
- end
-
- test "changing" do
- @klass.omg = "lol"
- assert_equal "lol", @klass.omg
- assert_equal "lol", @subklass.omg
- end
-
- test "changing in subclass" do
- @subklass.omg = "lol"
- assert_equal "lol", @subklass.omg
- assert_equal "default", @klass.omg
- end
-
- test "changing global" do
- @klass.wtf = "wtf"
- assert_equal "wtf", @klass.wtf
- assert_equal "wtf", @subklass.wtf
-
- @subklass.wtf = "lol"
- assert_equal "lol", @klass.wtf
- assert_equal "lol", @subklass.wtf
- end
-
- test "instance_writer" do
- obj = @klass.new
-
- @klass.omg2 = "omg"
- @klass.wtf2 = "wtf"
-
- assert_equal "omg", obj.omg2
- assert_equal "wtf", obj.wtf2
-
- obj.omg2 = "lol"
- obj.wtf2 = "lol"
-
- assert_equal "lol", obj.omg2
- assert_equal "lol", obj.wtf2
- assert_equal "omg", @klass.omg2
- assert_equal "lol", @klass.wtf2
- end
-end
diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb
index 7eb48abc3c..a93323a3a8 100644
--- a/activemodel/test/cases/serializers/xml_serialization_test.rb
+++ b/activemodel/test/cases/serializers/xml_serialization_test.rb
@@ -104,7 +104,7 @@ class XmlSerializationTest < ActiveModel::TestCase
assert_match %r{<createdAt}, @xml
end
- test "should use serialiable hash" do
+ test "should use serializable hash" do
@contact = SerializableContact.new
@contact.name = 'aaron stack'
@contact.age = 25
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index b7c2788db4..ed5f29cfe4 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,102 @@
## Rails 4.0.0 (unreleased) ##
+* Allow ActiveRecord::Relation#pluck to accept multiple columns. Returns an
+ array of arrays containing the typecasted values:
+
+ Person.pluck(:id, :name)
+ # SELECT people.id, people.name FROM people
+ # [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
+
+ *Jeroen van Ingen & Carlos Antonio da Silva*
+
+* Improve the derivation of HABTM join table name to take account of nesting.
+ It now takes the table names of the two models, sorts them lexically and
+ then joins them, stripping any common prefix from the second table name.
+
+ Some examples:
+
+ Top level models (Category <=> Product)
+ Old: categories_products
+ New: categories_products
+
+ Top level models with a global table_name_prefix (Category <=> Product)
+ Old: site_categories_products
+ New: site_categories_products
+
+ Nested models in a module without a table_name_prefix method (Admin::Category <=> Admin::Product)
+ Old: categories_products
+ New: categories_products
+
+ Nested models in a module with a table_name_prefix method (Admin::Category <=> Admin::Product)
+ Old: categories_products
+ New: admin_categories_products
+
+ Nested models in a parent model (Catalog::Category <=> Catalog::Product)
+ Old: categories_products
+ New: catalog_categories_products
+
+ Nested models in different parent models (Catalog::Category <=> Content::Page)
+ Old: categories_pages
+ New: catalog_categories_content_pages
+
+ *Andrew White*
+
+* Move HABTM validity checks to ActiveRecord::Reflection. One side effect of
+ this is to move when the exceptions are raised from the point of declaration
+ to when the association is built. This is consistant with other association
+ validity checks.
+
+ *Andrew White*
+
+* Added `stored_attributes` hash which contains the attributes stored using
+ ActiveRecord::Store. This allows you to retrieve the list of attributes
+ you've defined.
+
+ class User < ActiveRecord::Base
+ store :settings, accessors: [:color, :homepage]
+ end
+
+ User.stored_attributes[:settings] # [:color, :homepage]
+
+ *Joost Baaij & Carlos Antonio da Silva*
+
+* `composed_of` was removed. You'll have to write your own accessor
+ and mutator methods if you'd like to use value objects to represent some
+ portion of your models. So, instead of:
+
+ class Person < ActiveRecord::Base
+ composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
+ end
+
+ you could write something like this:
+
+ def address
+ @address ||= Address.new(address_street, address_city)
+ end
+
+ def address=(address)
+ self[:address_street] = @address.street
+ self[:address_city] = @address.city
+
+ @address = address
+ end
+
+ *Steve Klabnik*
+
+* PostgreSQL default log level is now 'warning', to bypass the noisy notice
+ messages. You can change the log level using the `min_messages` option
+ available in your config/database.yml.
+
+ *kennyj*
+
+* Add uuid datatype support to PostgreSQL adapter. *Konstantin Shabanov*
+
+* `update_attribute` has been removed. Use `update_column` if
+ you want to bypass mass-assignment protection, validations, callbacks,
+ and touching of updated_at. Otherwise please use `update_attributes`.
+
+ *Steve Klabnik*
+
* Added `ActiveRecord::Migration.check_pending!` that raises an error if
migrations are pending. *Richard Schneeman*
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
index d080e0b0f5..60965590a1 100644
--- a/activerecord/README.rdoc
+++ b/activerecord/README.rdoc
@@ -46,18 +46,6 @@ A short rundown of some of the major features:
{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
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index f8526bb691..a894d83ea7 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -36,7 +36,6 @@ module ActiveRecord
autoload :ConnectionNotEstablished, 'active_record/errors'
autoload :ConnectionAdapters, 'active_record/connection_adapters/abstract_adapter'
- autoload :Aggregations
autoload :Associations
autoload :AttributeMethods
autoload :AttributeAssignment
@@ -137,6 +136,16 @@ module ActiveRecord
end
end
+ module Tasks
+ extend ActiveSupport::Autoload
+
+ autoload :DatabaseTasks
+ autoload :SQLiteDatabaseTasks, 'active_record/tasks/sqlite_database_tasks'
+ autoload :MySQLDatabaseTasks, 'active_record/tasks/mysql_database_tasks'
+ autoload :PostgreSQLDatabaseTasks,
+ 'active_record/tasks/postgresql_database_tasks'
+ end
+
autoload :TestCase
autoload :TestFixtures, 'active_record/fixtures'
end
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
deleted file mode 100644
index 3ae7030caa..0000000000
--- a/activerecord/lib/active_record/aggregations.rb
+++ /dev/null
@@ -1,261 +0,0 @@
-module ActiveRecord
- # = Active Record Aggregations
- module Aggregations # :nodoc:
- extend ActiveSupport::Concern
-
- def clear_aggregation_cache #:nodoc:
- @aggregation_cache.clear if persisted?
- 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).
- #
- # class Customer < 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
- #
- # The customer class now has the following methods to manipulate the value objects:
- # * <tt>Customer#balance, Customer#balance=(money)</tt>
- # * <tt>Customer#address, Customer#address=(address)</tt>
- #
- # These methods will operate with value objects like the ones described below:
- #
- # class Money
- # include Comparable
- # attr_reader :amount, :currency
- # EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
- #
- # def initialize(amount, currency = "USD")
- # @amount, @currency = amount, currency
- # end
- #
- # def exchange_to(other_currency)
- # exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
- # Money.new(exchanged_amount, other_currency)
- # end
- #
- # def ==(other_money)
- # amount == other_money.amount && currency == other_money.currency
- # end
- #
- # def <=>(other_money)
- # if currency == other_money.currency
- # amount <=> other_money.amount
- # else
- # amount <=> other_money.exchange_to(currency).amount
- # end
- # end
- # end
- #
- # class Address
- # attr_reader :street, :city
- # def initialize(street, city)
- # @street, @city = street, city
- # end
- #
- # def close_to?(other_address)
- # city == other_address.city
- # end
- #
- # def ==(other_address)
- # city == other_address.city && street == other_address.street
- # 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 with any other attribute:
- #
- # customer.balance = Money.new(20) # sets the Money value object and the attribute
- # customer.balance # => Money value object
- # customer.balance.exchange_to("DKK") # => Money.new(120, "DKK")
- # customer.balance > Money.new(10) # => true
- # 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.
- #
- # customer.address_street = "Hyancintvej"
- # customer.address_city = "Copenhagen"
- # customer.address # => Address.new("Hyancintvej", "Copenhagen")
- #
- # customer.address_street = "Vesterbrogade"
- # customer.address # => Address.new("Hyancintvej", "Copenhagen")
- # customer.clear_aggregation_cache
- # customer.address # => Address.new("Vesterbrogade", "Copenhagen")
- #
- # customer.address = Address.new("May Street", "Chicago")
- # customer.address_street # => "May Street"
- # customer.address_city # => "Chicago"
- #
- # == 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.
- #
- # 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. The
- # Money#exchange_to method is an example of this. It 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.
- #
- # 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.
- #
- # 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:
- #
- # class NetworkResource < ActiveRecord::Base
- # composed_of :cidr,
- # :class_name => 'NetAddr::CIDR',
- # :mapping => [ %w(network_address network), %w(cidr_range bits) ],
- # :allow_nil => true,
- # :constructor => Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
- # :converter => Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
- # end
- #
- # # This calls the :constructor
- # network_resource = NetworkResource.new(:network_address => '192.168.0.1', :cidr_range => 24)
- #
- # # These assignments will both use the :converter
- # network_resource.cidr = [ '192.168.2.1', 8 ]
- # network_resource.cidr = '192.168.0.1/24'
- #
- # # This assignment won't use the :converter as the value is already an instance of the value class
- # network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8')
- #
- # # Saving and then reloading will use the :constructor on reload
- # network_resource.save
- # network_resource.reload
- #
- # == 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":
- #
- # Customer.where(:balance => Money.new(20, "USD")).all
- #
- module ClassMethods
- # Adds reader and writer methods for manipulating a value object:
- # <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 of the attribute in the value object. The
- # order in which mappings are defined determines 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.
- # 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.
- # 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>. If <tt>:allow_nil</tt> is set to true, the converter
- # can return nil to skip the assignment.
- #
- # Option examples:
- # composed_of :temperature, :mapping => %w(reading celsius)
- # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount),
- # :converter => Proc.new { |balance| balance.to_money }
- # composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
- # composed_of :gps_location
- # composed_of :gps_location, :allow_nil => true
- # composed_of :ip_address,
- # :class_name => 'IPAddr',
- # :mapping => %w(ip to_i),
- # :constructor => Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
- # :converter => Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
- #
- def composed_of(part_id, options = {})
- options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
-
- name = part_id.id2name
- class_name = options[:class_name] || name.camelize
- mapping = options[:mapping] || [ name, name ]
- mapping = [ mapping ] unless mapping.first.is_a?(Array)
- allow_nil = options[:allow_nil] || false
- constructor = options[:constructor] || :new
- converter = options[:converter]
-
- reader_method(name, class_name, mapping, allow_nil, constructor)
- writer_method(name, class_name, mapping, allow_nil, converter)
-
- create_reflection(:composed_of, part_id, options, self)
- end
-
- private
- def reader_method(name, class_name, mapping, allow_nil, constructor)
- define_method(name) do
- if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
- attrs = mapping.collect {|pair| read_attribute(pair.first)}
- object = constructor.respond_to?(:call) ?
- constructor.call(*attrs) :
- class_name.constantize.send(constructor, *attrs)
- @aggregation_cache[name] = object
- end
- @aggregation_cache[name]
- end
- end
-
- def writer_method(name, class_name, mapping, allow_nil, converter)
- define_method("#{name}=") do |part|
- klass = class_name.constantize
- unless part.is_a?(klass) || converter.nil? || part.nil?
- part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
- end
-
- if part.nil? && allow_nil
- mapping.each { |pair| self[pair.first] = nil }
- @aggregation_cache[name] = nil
- else
- mapping.each { |pair| self[pair.first] = part.send(pair.last) }
- @aggregation_cache[name] = part.freeze
- end
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index 30fc44b4c2..f7656ecd47 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -6,7 +6,6 @@ module ActiveRecord::Associations::Builder
def build
reflection = super
- check_validity(reflection)
define_destroy_hook
reflection
end
@@ -24,34 +23,5 @@ module ActiveRecord::Associations::Builder
RUBY
})
end
-
- # TODO: These checks should probably be moved into the Reflection, and we should not be
- # redefining the options[:join_table] value - instead we should define a
- # reflection.join_table method.
- def check_validity(reflection)
- if reflection.association_foreign_key == reflection.foreign_key
- raise ActiveRecord::HasAndBelongsToManyAssociationForeignKeyNeeded.new(reflection)
- end
-
- reflection.options[:join_table] ||= join_table_name(
- model.send(:undecorated_table_name, model.to_s),
- model.send(:undecorated_table_name, reflection.class_name)
- )
- end
-
- # Generates a join table name from two provided table names.
- # The names in the join table names end up in lexicographic order.
- #
- # join_table_name("members", "clubs") # => "clubs_members"
- # join_table_name("members", "special_clubs") # => "members_special_clubs"
- def join_table_name(first_table_name, second_table_name)
- if first_table_name < second_table_name
- join_table = "#{first_table_name}_#{second_table_name}"
- else
- join_table = "#{second_table_name}_#{first_table_name}"
- end
-
- model.table_name_prefix + join_table + model.table_name_suffix
- end
end
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index e94fe35170..2f6ddfeeb3 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -50,18 +50,7 @@ module ActiveRecord
end
else
column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
- relation = scoped
-
- including = (relation.eager_load_values + relation.includes_values).uniq
-
- if including.any?
- join_dependency = ActiveRecord::Associations::JoinDependency.new(reflection.klass, including, [])
- relation = join_dependency.join_associations.inject(relation) do |r, association|
- association.join_relation(r)
- end
- end
-
- relation.pluck(column)
+ scoped.pluck(column)
end
end
@@ -197,7 +186,7 @@ module ActiveRecord
if options[:uniq]
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
column_name ||= reflection.klass.primary_key
- count_options.merge!(:distinct => true)
+ count_options[:distinct] = true
end
value = scoped.count(column_name, count_options)
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 58d041ec1d..93618721bb 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
@@ -5,7 +5,7 @@ module ActiveRecord
attr_reader :join_table
def initialize(owner, reflection)
- @join_table = Arel::Table.new(reflection.options[:join_table])
+ @join_table = Arel::Table.new(reflection.join_table)
super
end
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 2131edbc20..f0d1120c68 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -36,7 +36,7 @@ module ActiveRecord
when :destroy
target.destroy
when :nullify
- target.update_attribute(reflection.foreign_key, nil)
+ target.update_column(reflection.foreign_key, nil)
end
end
end
diff --git a/activerecord/lib/active_record/associations/join_helper.rb b/activerecord/lib/active_record/associations/join_helper.rb
index cea6ad6944..5a41b40c8f 100644
--- a/activerecord/lib/active_record/associations/join_helper.rb
+++ b/activerecord/lib/active_record/associations/join_helper.rb
@@ -19,7 +19,7 @@ module ActiveRecord
if reflection.source_macro == :has_and_belongs_to_many
tables << alias_tracker.aliased_table_for(
- (reflection.source_reflection || reflection).options[:join_table],
+ (reflection.source_reflection || reflection).join_table,
table_alias_for(reflection, true)
)
end
diff --git a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb
index b77b667219..8e8925f0a9 100644
--- a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb
@@ -6,7 +6,7 @@ module ActiveRecord
def initialize(klass, records, reflection, preload_options)
super
- @join_table = Arel::Table.new(options[:join_table]).alias('t0')
+ @join_table = Arel::Table.new(reflection.join_table).alias('t0')
end
# Unlike the other associations, we want to get a raw array of rows so that we can
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index abc2fa546a..ab6d253ef8 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -1,11 +1,25 @@
require 'active_support/concern'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :whitelist_attributes, instance_accessor: false
+ mattr_accessor :mass_assignment_sanitizer, instance_accessor: false
+ end
+
module AttributeAssignment
extend ActiveSupport::Concern
include ActiveModel::MassAssignmentSecurity
+ included do
+ initialize_mass_assignment_sanitizer
+ end
+
module ClassMethods
+ def inherited(child) # :nodoc:
+ child.send :initialize_mass_assignment_sanitizer if self == Base
+ super
+ end
+
private
# The primary key and inheritance column can never be set by mass-assignment for security reasons.
@@ -14,6 +28,11 @@ module ActiveRecord
default << 'id' unless primary_key.eql? 'id'
default
end
+
+ def initialize_mass_assignment_sanitizer
+ attr_accessible(nil) if Model.whitelist_attributes
+ self.mass_assignment_sanitizer = Model.mass_assignment_sanitizer if Model.mass_assignment_sanitizer
+ end
end
# Allows you to set all the attributes at once by passing in a hash with keys
@@ -113,7 +132,7 @@ module ActiveRecord
private
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
- # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
+ # by calling new on the column type or aggregation type 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,
@@ -148,7 +167,7 @@ module ActiveRecord
end
def read_value_from_parameter(name, values_hash_from_param)
- klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
+ klass = column_for_attribute(name).klass
if values_hash_from_param.values.all?{|v|v.nil?}
nil
elsif klass == Time
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 172026d150..f36df4a444 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -16,19 +16,6 @@ module ActiveRecord
include TimeZoneConversion
include Dirty
include Serialization
-
- # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
- # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
- # (Alias for the protected read_attribute method).
- def [](attr_name)
- read_attribute(attr_name)
- end
-
- # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
- # (Alias for the protected write_attribute method).
- def []=(attr_name, value)
- write_attribute(attr_name, value)
- end
end
module ClassMethods
@@ -192,6 +179,19 @@ module ActiveRecord
self.class.columns_hash[name.to_s]
end
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
+ # (Alias for the protected read_attribute method).
+ def [](attr_name)
+ read_attribute(attr_name)
+ end
+
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
+ # (Alias for the protected write_attribute method).
+ def []=(attr_name, value)
+ write_attribute(attr_name, value)
+ end
+
protected
def clone_attributes(reader_method = :read_attribute, attributes = {})
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 11c63591e3..a24b4b7839 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -1,12 +1,18 @@
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/module/attribute_accessors'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :partial_updates, instance_accessor: false
+ self.partial_updates = true
+ end
+
module AttributeMethods
module Dirty
extend ActiveSupport::Concern
+
include ActiveModel::Dirty
- include AttributeMethods::Write
included do
if self < ::ActiveRecord::Timestamp
@@ -14,7 +20,6 @@ module ActiveRecord
end
config_attribute :partial_updates
- self.partial_updates = true
end
# Attempts to +save+ the record and clears changed attributes if successful.
@@ -72,11 +77,8 @@ module ActiveRecord
def _field_changed?(attr, old, value)
if column = column_for_attribute(attr)
- if column.number? && column.null && (old.nil? || old == 0) && value.blank?
- # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
- # Hence we don't record it as a change if the value changes from nil to ''.
- # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
- # be typecast back to 0 (''.to_i => 0)
+ if column.number? && (changes_from_nil_to_empty_string?(column, old, value) ||
+ changes_from_zero_to_string?(old, value))
value = nil
else
value = column.type_cast(value)
@@ -85,6 +87,19 @@ module ActiveRecord
old != value
end
+
+ def changes_from_nil_to_empty_string?(column, old, value)
+ # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
+ # Hence we don't record it as a change if the value changes from nil to ''.
+ # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
+ # be typecast back to 0 (''.to_i => 0)
+ column.null && (old.nil? || old == 0) && value.blank?
+ end
+
+ def changes_from_zero_to_string?(old, value)
+ # For columns with old 0 and value non-empty string
+ old == 0 && value.present? && value != '0'
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index dcc3d79de9..a7af086e43 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -1,13 +1,17 @@
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :attribute_types_cached_by_default, instance_accessor: false
+ end
+
module AttributeMethods
module Read
extend ActiveSupport::Concern
ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
+ ActiveRecord::Model.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
included do
- config_attribute :attribute_types_cached_by_default, :global => true
- self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
+ config_attribute :attribute_types_cached_by_default
end
module ClassMethods
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 706fbf0546..4af4d28b74 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -6,7 +6,7 @@ module ActiveRecord
included do
# Returns a hash of all the attributes that have been specified for serialization as
# keys and their class restriction as values.
- config_attribute :serialized_attributes
+ class_attribute :serialized_attributes
self.serialized_attributes = {}
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 58a5d82e14..e300c9721f 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -2,6 +2,14 @@ require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/object/inclusion'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :time_zone_aware_attributes, instance_accessor: false
+ self.time_zone_aware_attributes = false
+
+ mattr_accessor :skip_time_zone_conversion_for_attributes, instance_accessor: false
+ self.skip_time_zone_conversion_for_attributes = []
+ end
+
module AttributeMethods
module TimeZoneConversion
class Type # :nodoc:
@@ -22,11 +30,8 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- config_attribute :time_zone_aware_attributes, :global => true
- self.time_zone_aware_attributes = false
-
+ config_attribute :time_zone_aware_attributes, global: true
config_attribute :skip_time_zone_conversion_for_attributes
- self.skip_time_zone_conversion_for_attributes = []
end
module ClassMethods
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index b5e3f2b4b5..111208d0b9 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -231,30 +231,6 @@ module ActiveRecord
# Returns true or false depending on whether the proc is contained in the before_save callback chain on a Topic model.
#
module Callbacks
- # We can't define callbacks directly on ActiveRecord::Model because
- # it is a module. So we queue up the definitions and execute them
- # when ActiveRecord::Model is included.
- module Register #:nodoc:
- def self.extended(base)
- base.config_attribute :_callbacks_register
- base._callbacks_register = []
- end
-
- def self.setup(base)
- base._callbacks_register.each do |item|
- base.send(*item)
- end
- end
-
- def define_callbacks(*args)
- self._callbacks_register << [:define_callbacks, *args]
- end
-
- def define_model_callbacks(*args)
- self._callbacks_register << [:define_model_callbacks, *args]
- end
- end
-
extend ActiveSupport::Concern
CALLBACKS = [
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 61ff603006..347d794fa3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -448,11 +448,11 @@ module ActiveRecord
end
def new_connection
- ActiveRecord::Base.send(spec.adapter_method, spec.config)
+ ActiveRecord::Model.send(spec.adapter_method, spec.config)
end
def current_connection_id #:nodoc:
- ActiveRecord::Base.connection_id ||= Thread.current.object_id
+ ActiveRecord::Model.connection_id ||= Thread.current.object_id
end
def checkout_new_connection
@@ -563,10 +563,12 @@ module ActiveRecord
end
def retrieve_connection_pool(klass)
- pool = get_pool_for_class klass.name
- return pool if pool
- return nil if ActiveRecord::Model == klass
- retrieve_connection_pool klass.active_record_super
+ if !(klass < Model::Tag)
+ get_pool_for_class('ActiveRecord::Model') # default connection
+ else
+ pool = get_pool_for_class(klass.name)
+ pool || retrieve_connection_pool(klass.superclass)
+ end
end
private
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 4c6d03a1d2..b0b51f540c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -370,7 +370,7 @@ module ActiveRecord
records.uniq.each do |record|
begin
record.rolledback!(rollback)
- rescue Exception => e
+ rescue => e
record.logger.error(e) if record.respond_to?(:logger) && record.logger
end
end
@@ -385,7 +385,7 @@ module ActiveRecord
records.uniq.each do |record|
begin
record.committed!
- rescue Exception => e
+ rescue => e
record.logger.error(e) if record.respond_to?(:logger) && record.logger
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index c6faae77cc..28a9821913 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -286,7 +286,7 @@ module ActiveRecord
:name => name,
:connection_id => object_id,
:binds => binds) { yield }
- rescue Exception => e
+ rescue => e
message = "#{e.class.name}: #{e.message}: #{sql}"
@logger.error message if @logger
exception = translate_exception(e, message)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 692473abc5..921278d145 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -264,19 +264,19 @@ module ActiveRecord
def begin_db_transaction
execute "BEGIN"
- rescue Exception
+ rescue
# Transactions aren't supported
end
def commit_db_transaction #:nodoc:
execute "COMMIT"
- rescue Exception
+ rescue
# Transactions aren't supported
end
def rollback_db_transaction #:nodoc:
execute "ROLLBACK"
- rescue Exception
+ rescue
# Transactions aren't supported
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index df3d5e4657..6657491c06 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -223,6 +223,7 @@ module ActiveRecord
alias_type 'bit', 'text'
alias_type 'varbit', 'text'
alias_type 'macaddr', 'text'
+ alias_type 'uuid', 'text'
# FIXME: I don't think this is correct. We should probably be returning a parsed date,
# but the tests pass with a string returned.
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 03c318f5f7..43e243581c 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -89,7 +89,6 @@ module ActiveRecord
else
string
end
-
end
def cidr_to_string(object)
@@ -256,7 +255,7 @@ module ActiveRecord
:integer
# UUID type
when 'uuid'
- :string
+ :uuid
# Small and big integer types
when /^(?:small|big)int$/
:integer
@@ -319,6 +318,10 @@ module ActiveRecord
def macaddr(name, options = {})
column(name, 'macaddr', options)
end
+
+ def uuid(name, options = {})
+ column(name, 'uuid', options)
+ end
end
ADAPTER_NAME = 'PostgreSQL'
@@ -341,7 +344,8 @@ module ActiveRecord
:hstore => { :name => "hstore" },
:inet => { :name => "inet" },
:cidr => { :name => "cidr" },
- :macaddr => { :name => "macaddr" }
+ :macaddr => { :name => "macaddr" },
+ :uuid => { :name => "uuid" }
}
# Returns 'PostgreSQL' as adapter name for identification purposes.
@@ -1446,7 +1450,7 @@ module ActiveRecord
if @config[:encoding]
@connection.set_client_encoding(@config[:encoding])
end
- self.client_min_messages = @config[:min_messages] if @config[:min_messages]
+ self.client_min_messages = @config[:min_messages] || 'warning'
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
# Use standard-conforming strings if available so we don't have to do the E'...' dance.
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index 7b218a5570..bda41df80f 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -90,6 +90,10 @@ module ActiveRecord
connection_handler.remove_connection(klass)
end
+ def clear_cache! # :nodoc:
+ connection.schema_cache.clear!
+ end
+
delegate :clear_active_connections!, :clear_reloadable_connections!,
:clear_all_connections!, :to => :connection_handler
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index dbad561ca2..90f156456e 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -1,80 +1,88 @@
require 'active_support/concern'
require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/object/deep_dup'
+require 'active_support/core_ext/module/delegation'
require 'thread'
module ActiveRecord
- module Core
- extend ActiveSupport::Concern
+ ActiveSupport.on_load(:active_record_config) do
+ ##
+ # :singleton-method:
+ #
+ # Accepts a logger conforming to the interface of Log4r 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+.
+ mattr_accessor :logger, instance_accessor: false
- included do
- ##
- # :singleton-method:
- #
- # Accepts a logger conforming to the interface of Log4r 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+.
- config_attribute :logger, :global => true
+ ##
+ # :singleton-method:
+ # Contains the database configuration - as is typically stored in config/database.yml -
+ # as a Hash.
+ #
+ # For example, the following database.yml...
+ #
+ # development:
+ # adapter: sqlite3
+ # database: db/development.sqlite3
+ #
+ # production:
+ # adapter: sqlite3
+ # database: db/production.sqlite3
+ #
+ # ...would result in ActiveRecord::Base.configurations to look like this:
+ #
+ # {
+ # 'development' => {
+ # 'adapter' => 'sqlite3',
+ # 'database' => 'db/development.sqlite3'
+ # },
+ # 'production' => {
+ # 'adapter' => 'sqlite3',
+ # 'database' => 'db/production.sqlite3'
+ # }
+ # }
+ mattr_accessor :configurations, instance_accessor: false
+ self.configurations = {}
- ##
- # :singleton-method:
- # Contains the database configuration - as is typically stored in config/database.yml -
- # as a Hash.
- #
- # For example, the following database.yml...
- #
- # development:
- # adapter: sqlite3
- # database: db/development.sqlite3
- #
- # production:
- # adapter: sqlite3
- # database: db/production.sqlite3
- #
- # ...would result in ActiveRecord::Base.configurations to look like this:
- #
- # {
- # 'development' => {
- # 'adapter' => 'sqlite3',
- # 'database' => 'db/development.sqlite3'
- # },
- # 'production' => {
- # 'adapter' => 'sqlite3',
- # 'database' => 'db/production.sqlite3'
- # }
- # }
- config_attribute :configurations, :global => true
- self.configurations = {}
+ ##
+ # :singleton-method:
+ # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
+ # dates and times from the database. This is set to :utc by default.
+ mattr_accessor :default_timezone, instance_accessor: false
+ self.default_timezone = :utc
- ##
- # :singleton-method:
- # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
- # dates and times from the database. This is set to :utc by default.
- config_attribute :default_timezone, :global => true
- self.default_timezone = :utc
+ ##
+ # :singleton-method:
+ # Specifies the format to use when dumping the database schema with Rails'
+ # Rakefile. If :sql, the schema is dumped as (potentially database-
+ # specific) SQL statements. If :ruby, the schema is dumped as an
+ # ActiveRecord::Schema file which can be loaded into any database that
+ # supports migrations. Use :ruby if you want to have different database
+ # adapters for, e.g., your development and test environments.
+ mattr_accessor :schema_format, instance_accessor: false
+ self.schema_format = :ruby
- ##
- # :singleton-method:
- # Specifies the format to use when dumping the database schema with Rails'
- # Rakefile. If :sql, the schema is dumped as (potentially database-
- # specific) SQL statements. If :ruby, the schema is dumped as an
- # ActiveRecord::Schema file which can be loaded into any database that
- # supports migrations. Use :ruby if you want to have different database
- # adapters for, e.g., your development and test environments.
- config_attribute :schema_format, :global => true
- self.schema_format = :ruby
+ ##
+ # :singleton-method:
+ # Specify whether or not to use timestamps for migration versions
+ mattr_accessor :timestamped_migrations, instance_accessor: false
+ self.timestamped_migrations = true
- ##
- # :singleton-method:
- # Specify whether or not to use timestamps for migration versions
- config_attribute :timestamped_migrations, :global => true
- self.timestamped_migrations = true
+ mattr_accessor :connection_handler, instance_accessor: false
+ self.connection_handler = ConnectionAdapters::ConnectionHandler.new
+
+ mattr_accessor :dependent_restrict_raises, instance_accessor: false
+ self.dependent_restrict_raises = true
+ end
+ module Core
+ extend ActiveSupport::Concern
+
+ included do
##
# :singleton-method:
# The connection handler
config_attribute :connection_handler
- self.connection_handler = ConnectionAdapters::ConnectionHandler.new
##
# :singleton-method:
@@ -83,8 +91,11 @@ module ActiveRecord
# ActiveRecord::DeleteRestrictionError exception will be raised
# along with a DEPRECATION WARNING. If set to false, an error would
# be added to the model instead.
- config_attribute :dependent_restrict_raises, :global => true
- self.dependent_restrict_raises = true
+ config_attribute :dependent_restrict_raises
+
+ %w(logger configurations default_timezone schema_format timestamped_migrations).each do |name|
+ config_attribute name, global: true
+ end
end
module ClassMethods
@@ -255,7 +266,6 @@ module ActiveRecord
@changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
end
- @aggregation_cache = {}
@association_cache = {}
@attributes_cache = {}
@@ -380,7 +390,6 @@ module ActiveRecord
@attributes[pk] = nil unless @attributes.key?(pk)
- @aggregation_cache = {}
@association_cache = {}
@attributes_cache = {}
@previously_changed = {}
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index b163ef3c12..b27a19f89a 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -1,111 +1,115 @@
module ActiveRecord
# = Active Record Counter Cache
module CounterCache
- # Resets one or more counter caches to their correct value using an SQL
- # count query. This is useful when adding new counter caches, or if the
- # counter has been corrupted or modified directly by SQL.
- #
- # ==== Parameters
- #
- # * +id+ - The id of the object you wish to reset a counter on.
- # * +counters+ - One or more counter names to reset
- #
- # ==== Examples
- #
- # # For Post with id #1 records reset the comments_count
- # Post.reset_counters(1, :comments)
- def reset_counters(id, *counters)
- object = find(id)
- counters.each do |association|
- has_many_association = reflect_on_association(association.to_sym)
+ extend ActiveSupport::Concern
- foreign_key = has_many_association.foreign_key.to_s
- child_class = has_many_association.klass
- belongs_to = child_class.reflect_on_all_associations(:belongs_to)
- reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key }
- counter_name = reflection.counter_cache_column
+ module ClassMethods
+ # Resets one or more counter caches to their correct value using an SQL
+ # count query. This is useful when adding new counter caches, or if the
+ # counter has been corrupted or modified directly by SQL.
+ #
+ # ==== Parameters
+ #
+ # * +id+ - The id of the object you wish to reset a counter on.
+ # * +counters+ - One or more counter names to reset
+ #
+ # ==== Examples
+ #
+ # # For Post with id #1 records reset the comments_count
+ # Post.reset_counters(1, :comments)
+ def reset_counters(id, *counters)
+ object = find(id)
+ counters.each do |association|
+ has_many_association = reflect_on_association(association.to_sym)
- stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
- arel_table[counter_name] => object.send(association).count
- })
- connection.update stmt
- end
- return true
- end
+ foreign_key = has_many_association.foreign_key.to_s
+ child_class = has_many_association.klass
+ belongs_to = child_class.reflect_on_all_associations(:belongs_to)
+ reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key }
+ counter_name = reflection.counter_cache_column
- # A generic "counter updater" implementation, intended primarily to be
- # used by increment_counter and decrement_counter, but which may also
- # be useful on its own. It simply does a direct SQL update for the record
- # with the given ID, altering the given hash of counters by the amount
- # given by the corresponding value:
- #
- # ==== Parameters
- #
- # * +id+ - The id of the object you wish to update a counter on or an Array of ids.
- # * +counters+ - An Array of Hashes containing the names of the fields
- # to update as keys and the amount to update the field by as values.
- #
- # ==== Examples
- #
- # # For the Post with id of 5, decrement the comment_count by 1, and
- # # increment the action_count by 1
- # Post.update_counters 5, :comment_count => -1, :action_count => 1
- # # Executes the following SQL:
- # # UPDATE posts
- # # SET comment_count = COALESCE(comment_count, 0) - 1,
- # # action_count = COALESCE(action_count, 0) + 1
- # # WHERE id = 5
- #
- # # For the Posts with id of 10 and 15, increment the comment_count by 1
- # Post.update_counters [10, 15], :comment_count => 1
- # # Executes the following SQL:
- # # UPDATE posts
- # # SET comment_count = COALESCE(comment_count, 0) + 1
- # # WHERE id IN (10, 15)
- def update_counters(id, counters)
- updates = counters.map do |counter_name, value|
- operator = value < 0 ? '-' : '+'
- quoted_column = connection.quote_column_name(counter_name)
- "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
+ stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
+ arel_table[counter_name] => object.send(association).count
+ })
+ connection.update stmt
+ end
+ return true
end
- where(primary_key => id).update_all updates.join(', ')
- end
+ # A generic "counter updater" implementation, intended primarily to be
+ # used by increment_counter and decrement_counter, but which may also
+ # be useful on its own. It simply does a direct SQL update for the record
+ # with the given ID, altering the given hash of counters by the amount
+ # given by the corresponding value:
+ #
+ # ==== Parameters
+ #
+ # * +id+ - The id of the object you wish to update a counter on or an Array of ids.
+ # * +counters+ - An Array of Hashes containing the names of the fields
+ # to update as keys and the amount to update the field by as values.
+ #
+ # ==== Examples
+ #
+ # # For the Post with id of 5, decrement the comment_count by 1, and
+ # # increment the action_count by 1
+ # Post.update_counters 5, :comment_count => -1, :action_count => 1
+ # # Executes the following SQL:
+ # # UPDATE posts
+ # # SET comment_count = COALESCE(comment_count, 0) - 1,
+ # # action_count = COALESCE(action_count, 0) + 1
+ # # WHERE id = 5
+ #
+ # # For the Posts with id of 10 and 15, increment the comment_count by 1
+ # Post.update_counters [10, 15], :comment_count => 1
+ # # Executes the following SQL:
+ # # UPDATE posts
+ # # SET comment_count = COALESCE(comment_count, 0) + 1
+ # # WHERE id IN (10, 15)
+ def update_counters(id, counters)
+ updates = counters.map do |counter_name, value|
+ operator = value < 0 ? '-' : '+'
+ quoted_column = connection.quote_column_name(counter_name)
+ "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
+ end
- # Increment a number field by one, usually representing a count.
- #
- # This is used for caching aggregate values, so that they don't need to be computed every time.
- # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
- # shown it would have to run an SQL query to find how many posts and comments there are.
- #
- # ==== Parameters
- #
- # * +counter_name+ - The name of the field that should be incremented.
- # * +id+ - The id of the object that should be incremented.
- #
- # ==== Examples
- #
- # # Increment the post_count column for the record with an id of 5
- # DiscussionBoard.increment_counter(:post_count, 5)
- def increment_counter(counter_name, id)
- update_counters(id, counter_name => 1)
- end
+ where(primary_key => id).update_all updates.join(', ')
+ end
+
+ # Increment a number field by one, usually representing a count.
+ #
+ # This is used for caching aggregate values, so that they don't need to be computed every time.
+ # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
+ # shown it would have to run an SQL query to find how many posts and comments there are.
+ #
+ # ==== Parameters
+ #
+ # * +counter_name+ - The name of the field that should be incremented.
+ # * +id+ - The id of the object that should be incremented.
+ #
+ # ==== Examples
+ #
+ # # Increment the post_count column for the record with an id of 5
+ # DiscussionBoard.increment_counter(:post_count, 5)
+ def increment_counter(counter_name, id)
+ update_counters(id, counter_name => 1)
+ end
- # Decrement a number field by one, usually representing a count.
- #
- # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
- #
- # ==== Parameters
- #
- # * +counter_name+ - The name of the field that should be decremented.
- # * +id+ - The id of the object that should be decremented.
- #
- # ==== Examples
- #
- # # Decrement the post_count column for the record with an id of 5
- # DiscussionBoard.decrement_counter(:post_count, 5)
- def decrement_counter(counter_name, id)
- update_counters(id, counter_name => -1)
+ # Decrement a number field by one, usually representing a count.
+ #
+ # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
+ #
+ # ==== Parameters
+ #
+ # * +counter_name+ - The name of the field that should be decremented.
+ # * +id+ - The id of the object that should be decremented.
+ #
+ # ==== Examples
+ #
+ # # Decrement the post_count column for the record with an id of 5
+ # DiscussionBoard.decrement_counter(:post_count, 5)
+ def decrement_counter(counter_name, id)
+ update_counters(id, counter_name => -1)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb
index e278e62ce7..a37cde77ee 100644
--- a/activerecord/lib/active_record/dynamic_matchers.rb
+++ b/activerecord/lib/active_record/dynamic_matchers.rb
@@ -53,10 +53,11 @@ module ActiveRecord
@model = model
@name = name.to_s
@attribute_names = @name.match(self.class.pattern)[1].split('_and_')
+ @attribute_names.map! { |n| @model.attribute_aliases[n] || n }
end
def valid?
- attribute_names.all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) }
+ attribute_names.all? { |name| model.columns_hash[name] }
end
def define
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 9b88bb8178..858b667e22 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -196,4 +196,7 @@ module ActiveRecord
"Unknown primary key for table #{model.table_name} in model #{model}."
end
end
+
+ class ImmutableRelation < ActiveRecordError
+ end
end
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index b0eda8ef34..7ade385c70 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -1,12 +1,13 @@
+require 'active_support/lazy_load_hooks'
require 'active_support/core_ext/class/attribute'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :auto_explain_threshold_in_seconds, instance_accessor: false
+ end
+
module Explain
- def self.extended(base)
- # If a query takes longer than these many seconds we log its query plan
- # automatically. nil disables this feature.
- base.config_attribute :auto_explain_threshold_in_seconds, :global => true
- end
+ delegate :auto_explain_threshold_in_seconds, :auto_explain_threshold_in_seconds=, to: 'ActiveRecord::Model'
# If auto explain is enabled, this method triggers EXPLAIN logging for the
# queries triggered by the block if it takes more than the threshold as a
diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb
index 1f8c4fc203..d5ba343b4c 100644
--- a/activerecord/lib/active_record/explain_subscriber.rb
+++ b/activerecord/lib/active_record/explain_subscriber.rb
@@ -2,9 +2,12 @@ require 'active_support/notifications'
module ActiveRecord
class ExplainSubscriber # :nodoc:
- def call(*args)
+ def start(name, id, payload)
+ # unused
+ end
+
+ def finish(name, id, payload)
if queries = Thread.current[:available_queries_for_explain]
- payload = args.last
queries << payload.values_at(:sql, :binds) unless ignore_payload?(payload)
end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 7e6512501c..96d24b72b3 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -594,7 +594,7 @@ module ActiveRecord
when :has_and_belongs_to_many
if (targets = row.delete(association.name.to_s))
targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
- table_name = association.options[:join_table]
+ table_name = association.join_table
rows[table_name].concat targets.map { |target|
{ association.foreign_key => row[primary_key_name],
association.association_foreign_key => ActiveRecord::Fixtures.identify(target) }
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index 46d253b0a7..770083ac13 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -1,13 +1,17 @@
require 'active_support/concern'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ # Determine whether to store the full constant name including namespace when using STI
+ mattr_accessor :store_full_sti_class, instance_accessor: false
+ self.store_full_sti_class = true
+ end
+
module Inheritance
extend ActiveSupport::Concern
included do
- # Determine whether to store the full constant name including namespace when using STI
config_attribute :store_full_sti_class
- self.store_full_sti_class = true
end
module ClassMethods
@@ -95,7 +99,7 @@ module ActiveRecord
# Returns the class descending directly from ActiveRecord::Base or an
# abstract class, if any, in the inheritance hierarchy.
def class_of_active_record_descendant(klass)
- unless klass < Model
+ unless klass < Model::Tag
raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
end
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 05e052b953..4ce42feb74 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -1,4 +1,9 @@
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :lock_optimistically, instance_accessor: false
+ self.lock_optimistically = true
+ end
+
module Locking
# == What is Optimistic Locking
#
@@ -51,8 +56,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- config_attribute :lock_optimistically, :global => true
- self.lock_optimistically = true
+ config_attribute :lock_optimistically
end
def locking_enabled? #:nodoc:
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index acec65991d..d58176bc62 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -238,7 +238,7 @@ module ActiveRecord
# add_column :people, :salary, :integer
# Person.reset_column_information
# Person.all.each do |p|
- # p.update_attribute :salary, SalaryCalculator.compute(p)
+ # p.update_column :salary, SalaryCalculator.compute(p)
# end
# end
# end
@@ -258,7 +258,7 @@ module ActiveRecord
# ...
# say_with_time "Updating salaries..." do
# Person.all.each do |p|
- # p.update_attribute :salary, SalaryCalculator.compute(p)
+ # p.update_column :salary, SalaryCalculator.compute(p)
# end
# end
# ...
diff --git a/activerecord/lib/active_record/model.rb b/activerecord/lib/active_record/model.rb
index 105d1e0e2b..7b3d926d91 100644
--- a/activerecord/lib/active_record/model.rb
+++ b/activerecord/lib/active_record/model.rb
@@ -1,6 +1,34 @@
require 'active_support/deprecation'
+require 'active_support/concern'
+require 'active_support/core_ext/module/delegation'
+require 'active_support/core_ext/module/attribute_accessors'
module ActiveRecord
+ module Configuration # :nodoc:
+ # This just abstracts out how we define configuration options in AR. Essentially we
+ # have mattr_accessors on the ActiveRecord:Model constant that define global defaults.
+ # Classes that then use AR get class_attributes defined, which means that when they
+ # are assigned the default will be overridden for that class and subclasses. (Except
+ # when options[:global] == true, in which case there is one global value always.)
+ def config_attribute(name, options = {})
+ if options[:global]
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def self.#{name}; ActiveRecord::Model.#{name}; end
+ def #{name}; ActiveRecord::Model.#{name}; end
+ def self.#{name}=(val); ActiveRecord::Model.#{name} = val; end
+ CODE
+ else
+ options[:instance_writer] ||= false
+ class_attribute name, options
+
+ singleton_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ remove_method :#{name}
+ def #{name}; ActiveRecord::Model.#{name}; end
+ CODE
+ end
+ end
+ end
+
# <tt>ActiveRecord::Model</tt> can be included into a class to add Active Record persistence.
# This is an alternative to inheriting from <tt>ActiveRecord::Base</tt>. Example:
#
@@ -9,41 +37,35 @@ module ActiveRecord
# end
#
module Model
- module ClassMethods #:nodoc:
- include ActiveSupport::Callbacks::ClassMethods
- include ActiveModel::Naming
- include QueryCache::ClassMethods
- include ActiveSupport::Benchmarkable
- include ActiveSupport::DescendantsTracker
-
- include Querying
- include Translation
- include DynamicMatchers
- include CounterCache
- include Explain
- include ConnectionHandling
- end
+ extend ActiveSupport::Concern
+ extend ConnectionHandling
+ extend ActiveModel::Observing::ClassMethods
- def self.included(base)
- return if base.singleton_class < ClassMethods
+ # This allows us to detect an ActiveRecord::Model while it's in the process of being included.
+ module Tag; end
+ def self.append_features(base)
base.class_eval do
- extend ClassMethods
- Callbacks::Register.setup(self)
- initialize_generated_modules unless self == Base
+ include Tag
+ extend Configuration
end
+
+ super
end
- extend ActiveModel::Configuration
- extend ActiveModel::Callbacks
- extend ActiveModel::MassAssignmentSecurity::ClassMethods
- extend ActiveModel::AttributeMethods::ClassMethods
- extend Callbacks::Register
- extend Explain
- extend ConnectionHandling
+ included do
+ extend ActiveModel::Naming
+ extend ActiveSupport::Benchmarkable
+ extend ActiveSupport::DescendantsTracker
+
+ extend QueryCache::ClassMethods
+ extend Querying
+ extend Translation
+ extend DynamicMatchers
+ extend Explain
+ extend ConnectionHandling
- def self.extend(*modules)
- ClassMethods.send(:include, *modules)
+ initialize_generated_modules unless self == Base
end
include Persistence
@@ -56,13 +78,21 @@ module ActiveRecord
include AttributeAssignment
include ActiveModel::Conversion
include Validations
- include Locking::Optimistic, Locking::Pessimistic
+ include CounterCache
+ include Locking::Optimistic
+ include Locking::Pessimistic
include AttributeMethods
- include Callbacks, ActiveModel::Observing, Timestamp
+ include Callbacks
+ include ActiveModel::Observing
+ include Timestamp
include Associations
include ActiveModel::SecurePassword
- include AutosaveAssociation, NestedAttributes
- include Aggregations, Transactions, Reflection, Serialization, Store
+ include AutosaveAssociation
+ include NestedAttributes
+ include Transactions
+ include Reflection
+ include Serialization
+ include Store
include Core
class << self
@@ -103,6 +133,15 @@ module ActiveRecord
end
end
+ # This hook is where config accessors on Model get defined.
+ #
+ # We don't want to just open the Model module and add stuff to it in other files, because
+ # that would cause Model to load, which causes all sorts of loading order issues.
+ #
+ # We need this hook rather than just using the :active_record one, because users of the
+ # :active_record hook may need to use config options.
+ ActiveSupport.run_load_hooks(:active_record_config, Model)
+
# Load Base at this point, because the active_record load hook is run in that file.
Base
end
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 7f38dda11e..e6b76ddc4c 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -1,7 +1,19 @@
require 'active_support/concern'
-require 'active_support/core_ext/class/attribute_accessors'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :primary_key_prefix_type, instance_accessor: false
+
+ mattr_accessor :table_name_prefix, instance_accessor: false
+ self.table_name_prefix = ""
+
+ mattr_accessor :table_name_suffix, instance_accessor: false
+ self.table_name_suffix = ""
+
+ mattr_accessor :pluralize_table_names, instance_accessor: false
+ self.pluralize_table_names = true
+ end
+
module ModelSchema
extend ActiveSupport::Concern
@@ -13,7 +25,7 @@ module ActiveRecord
# 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.
- config_attribute :primary_key_prefix_type, :global => true
+ config_attribute :primary_key_prefix_type, global: true
##
# :singleton-method:
@@ -26,14 +38,12 @@ module ActiveRecord
# a namespace by defining a singleton method in the parent module called table_name_prefix which
# returns your chosen prefix.
config_attribute :table_name_prefix
- self.table_name_prefix = ""
##
# :singleton-method:
# Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
# "people_basecamp"). By default, the suffix is the empty string.
config_attribute :table_name_suffix
- self.table_name_suffix = ""
##
# :singleton-method:
@@ -41,7 +51,6 @@ module ActiveRecord
# If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
# See table_name for the full rules on table/class naming. This is true, by default.
config_attribute :pluralize_table_names
- self.pluralize_table_names = true
end
module ClassMethods
@@ -308,10 +317,6 @@ module ActiveRecord
@relation = nil
end
- def clear_cache! # :nodoc:
- connection.schema_cache.clear!
- end
-
private
# Guesses the table name, but does not decorate it with prefix and suffix information.
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 95a2ddcc11..841681e542 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -5,6 +5,11 @@ require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/class/attribute'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :nested_attributes_options, instance_accessor: false
+ self.nested_attributes_options = {}
+ end
+
module NestedAttributes #:nodoc:
class TooManyRecords < ActiveRecordError
end
@@ -13,7 +18,6 @@ module ActiveRecord
included do
config_attribute :nested_attributes_options
- self.nested_attributes_options = {}
end
# = Active Record Nested Attributes
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index ec5670ba6e..a23597be28 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -167,21 +167,6 @@ module ActiveRecord
became
end
- # Updates a single attribute and saves the record.
- # This is especially useful for boolean flags on existing records. Also note that
- #
- # * Validation is skipped.
- # * Callbacks are invoked.
- # * updated_at/updated_on column is updated if that column is available.
- # * Updates all the attributes that are dirty in this object.
- #
- def update_attribute(name, value)
- name = name.to_s
- verify_readonly_attribute(name)
- send("#{name}=", value)
- save(:validate => false)
- end
-
# Updates a single attribute of an object, without calling save.
#
# * Validation is skipped.
@@ -240,7 +225,7 @@ module ActiveRecord
# Saving is not subjected to validation checks. Returns +true+ if the
# record could be saved.
def increment!(attribute, by = 1)
- increment(attribute, by).update_attribute(attribute, self[attribute])
+ increment(attribute, by).update_column(attribute, self[attribute])
end
# Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1).
@@ -257,7 +242,7 @@ module ActiveRecord
# Saving is not subjected to validation checks. Returns +true+ if the
# record could be saved.
def decrement!(attribute, by = 1)
- decrement(attribute, by).update_attribute(attribute, self[attribute])
+ decrement(attribute, by).update_column(attribute, self[attribute])
end
# Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
@@ -274,7 +259,7 @@ module ActiveRecord
# Saving is not subjected to validation checks. Returns +true+ if the
# record could be saved.
def toggle!(attribute)
- toggle(attribute).update_attribute(attribute, self[attribute])
+ toggle(attribute).update_column(attribute, self[attribute])
end
# Reloads the attributes of this object from the database.
@@ -282,7 +267,6 @@ module ActiveRecord
# may do e.g. record.reload(:lock => true) to reload the same record with
# an exclusive row lock.
def reload(options = nil)
- clear_aggregation_cache
clear_association_cache
fresh_object =
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 319516413b..9432a70c41 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -68,9 +68,6 @@ module ActiveRecord
initializer "active_record.set_configs" do |app|
ActiveSupport.on_load(:active_record) do
- if app.config.active_record.delete(:whitelist_attributes)
- attr_accessible(nil)
- end
app.config.active_record.each do |k,v|
send "#{k}=", v
end
@@ -97,18 +94,12 @@ module ActiveRecord
end
initializer "active_record.set_reloader_hooks" do |app|
- hook = lambda do
- ActiveRecord::Base.clear_reloadable_connections!
- ActiveRecord::Base.clear_cache!
- end
+ hook = app.config.reload_classes_only_on_change ? :to_prepare : :to_cleanup
- if app.config.reload_classes_only_on_change
- ActiveSupport.on_load(:active_record) do
- ActionDispatch::Reloader.to_prepare(&hook)
- end
- else
- ActiveSupport.on_load(:active_record) do
- ActionDispatch::Reloader.to_cleanup(&hook)
+ ActiveSupport.on_load(:active_record) do
+ ActionDispatch::Reloader.send(hook) do
+ ActiveRecord::Model.clear_reloadable_connections!
+ ActiveRecord::Model.clear_cache!
end
end
end
@@ -119,20 +110,21 @@ module ActiveRecord
config.after_initialize do |app|
ActiveSupport.on_load(:active_record) do
- ActiveRecord::Base.instantiate_observers
+ ActiveRecord::Model.instantiate_observers
ActionDispatch::Reloader.to_prepare do
- ActiveRecord::Base.instantiate_observers
+ ActiveRecord::Model.instantiate_observers
end
end
ActiveSupport.on_load(:active_record) do
if app.config.use_schema_cache_dump
filename = File.join(app.config.paths["db"].first, "schema_cache.dump")
+
if File.file?(filename)
cache = Marshal.load File.binread filename
if cache.version == ActiveRecord::Migrator.current_version
- ActiveRecord::Base.connection.schema_cache = cache
+ ActiveRecord::Model.connection.schema_cache = cache
else
warn "schema_cache.dump is expired. Current version is #{ActiveRecord::Migrator.current_version}, but cache version is #{cache.version}."
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index d8d4834d22..8199b5c2e0 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -14,138 +14,27 @@ db_namespace = namespace :db do
end
namespace :create do
- # desc 'Create all the local databases defined in config/database.yml'
task :all => :load_config do
- ActiveRecord::Base.configurations.each_value do |config|
- # Skip entries that don't have a database key, such as the first entry here:
- #
- # defaults: &defaults
- # adapter: mysql
- # username: root
- # password:
- # host: localhost
- #
- # development:
- # database: blog_development
- # *defaults
- next unless config['database']
- # Only connect to local databases
- local_database?(config) { create_database(config) }
- end
+ ActiveRecord::Tasks::DatabaseTasks.create_all
end
end
desc 'Create the database from config/database.yml for the current Rails.env (use db:create:all to create all dbs in the config)'
task :create => :load_config do
- configs_for_environment.each { |config| create_database(config) }
- ActiveRecord::Base.establish_connection(configs_for_environment.first)
- end
-
- def mysql_creation_options(config)
- @charset = ENV['CHARSET'] || 'utf8'
- @collation = ENV['COLLATION'] || 'utf8_unicode_ci'
- {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)}
- end
-
- def create_database(config)
- begin
- if config['adapter'] =~ /sqlite/
- if File.exist?(config['database'])
- $stderr.puts "#{config['database']} already exists"
- else
- begin
- # Create the SQLite database
- ActiveRecord::Base.establish_connection(config)
- ActiveRecord::Base.connection
- rescue Exception => e
- $stderr.puts e, *(e.backtrace)
- $stderr.puts "Couldn't create database for #{config.inspect}"
- end
- end
- return # Skip the else clause of begin/rescue
- else
- ActiveRecord::Base.establish_connection(config)
- ActiveRecord::Base.connection
- end
- rescue
- case config['adapter']
- when /mysql/
- if config['adapter'] =~ /jdbc/
- #FIXME After Jdbcmysql gives this class
- require 'active_record/railties/jdbcmysql_error'
- error_class = ArJdbcMySQL::Error
- else
- error_class = config['adapter'] =~ /mysql2/ ? Mysql2::Error : Mysql::Error
- end
- access_denied_error = 1045
- begin
- ActiveRecord::Base.establish_connection(config.merge('database' => nil))
- ActiveRecord::Base.connection.create_database(config['database'], mysql_creation_options(config))
- ActiveRecord::Base.establish_connection(config)
- rescue error_class => sqlerr
- if sqlerr.errno == access_denied_error
- print "#{sqlerr.error}. \nPlease provide the root password for your mysql installation\n>"
- root_password = $stdin.gets.strip
- grant_statement = "GRANT ALL PRIVILEGES ON #{config['database']}.* " \
- "TO '#{config['username']}'@'localhost' " \
- "IDENTIFIED BY '#{config['password']}' WITH GRANT OPTION;"
- ActiveRecord::Base.establish_connection(config.merge(
- 'database' => nil, 'username' => 'root', 'password' => root_password))
- ActiveRecord::Base.connection.create_database(config['database'], mysql_creation_options(config))
- ActiveRecord::Base.connection.execute grant_statement
- ActiveRecord::Base.establish_connection(config)
- else
- $stderr.puts sqlerr.error
- $stderr.puts "Couldn't create database for #{config.inspect}, charset: #{config['charset'] || @charset}, collation: #{config['collation'] || @collation}"
- $stderr.puts "(if you set the charset manually, make sure you have a matching collation)" if config['charset']
- end
- end
- when /postgresql/
- @encoding = config['encoding'] || ENV['CHARSET'] || 'utf8'
- begin
- ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
- ActiveRecord::Base.connection.create_database(config['database'], config.merge('encoding' => @encoding))
- ActiveRecord::Base.establish_connection(config)
- rescue Exception => e
- $stderr.puts e, *(e.backtrace)
- $stderr.puts "Couldn't create database for #{config.inspect}"
- end
- end
- else
- $stderr.puts "#{config['database']} already exists"
- end
+ ActiveRecord::Tasks::DatabaseTasks.create_current
end
namespace :drop do
- # desc 'Drops all the local databases defined in config/database.yml'
task :all => :load_config do
- ActiveRecord::Base.configurations.each_value do |config|
- # Skip entries that don't have a database key
- next unless config['database']
- begin
- # Only connect to local databases
- local_database?(config) { drop_database(config) }
- rescue Exception => e
- $stderr.puts "Couldn't drop #{config['database']} : #{e.inspect}"
- end
- end
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
end
end
desc 'Drops the database for the current Rails.env (use db:drop:all to drop all databases)'
task :drop => :load_config do
- configs_for_environment.each { |config| drop_database_and_rescue(config) }
- end
-
- def local_database?(config, &block)
- if config['host'].in?(['127.0.0.1', 'localhost']) || config['host'].blank?
- yield
- else
- $stderr.puts "This task only modifies local databases. #{config['database']} is on a remote host."
- end
+ ActiveRecord::Tasks::DatabaseTasks.drop_current
end
-
desc "Migrate the database (options: VERSION=x, VERBOSE=false)."
task :migrate => [:environment, :load_config] do
ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
@@ -247,31 +136,18 @@ db_namespace = namespace :db do
end
# desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.'
- task :reset => :environment do
+ task :reset => [:environment, :load_config] do
db_namespace["drop"].invoke
db_namespace["setup"].invoke
end
# desc "Retrieves the charset for the current environment's database"
- task :charset => :environment do
- config = ActiveRecord::Base.configurations[Rails.env || 'development']
- case config['adapter']
- when /mysql/
- ActiveRecord::Base.establish_connection(config)
- puts ActiveRecord::Base.connection.charset
- when /postgresql/
- ActiveRecord::Base.establish_connection(config)
- puts ActiveRecord::Base.connection.encoding
- when /sqlite/
- ActiveRecord::Base.establish_connection(config)
- puts ActiveRecord::Base.connection.encoding
- else
- $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
- end
+ task :charset => [:environment, :load_config] do
+ puts ActiveRecord::Tasks::DatabaseTasks.charset_current
end
# desc "Retrieves the collation for the current environment's database"
- task :collation => :environment do
+ task :collation => [:environment, :load_config] do
config = ActiveRecord::Base.configurations[Rails.env || 'development']
case config['adapter']
when /mysql/
@@ -283,12 +159,12 @@ db_namespace = namespace :db do
end
desc 'Retrieves the current schema version number'
- task :version => :environment do
+ task :version => [:environment, :load_config] do
puts "Current version: #{ActiveRecord::Migrator.current_version}"
end
# desc "Raises an error if there are pending migrations"
- task :abort_if_pending_migrations => :environment do
+ task :abort_if_pending_migrations => [:environment, :load_config] do
pending_migrations = ActiveRecord::Migrator.new(:up, ActiveRecord::Migrator.migrations_paths).pending_migrations
if pending_migrations.any?
@@ -311,20 +187,20 @@ db_namespace = namespace :db do
namespace :fixtures do
desc "Load fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
- task :load => :environment do
+ task :load => [:environment, :load_config] do
require 'active_record/fixtures'
ActiveRecord::Base.establish_connection(Rails.env)
base_dir = File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten
fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact
- (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.{yml,csv}"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
+ (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
ActiveRecord::Fixtures.create_fixtures(fixtures_dir, fixture_file)
end
end
# desc "Search for a fixture given a LABEL or ID. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
- task :identify => :environment do
+ task :identify => [:environment, :load_config] do
require 'active_record/fixtures'
label, id = ENV['LABEL'], ENV['ID']
@@ -360,7 +236,7 @@ db_namespace = namespace :db do
end
desc 'Load a schema.rb file into the database'
- task :load => :environment do
+ task :load => [:environment, :load_config] do
file = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb"
if File.exists?(file)
load(file)
@@ -375,7 +251,7 @@ db_namespace = namespace :db do
namespace :cache do
desc 'Create a db/schema_cache.dump file.'
- task :dump => :environment do
+ task :dump => [:environment, :load_config] do
con = ActiveRecord::Base.connection
filename = File.join(Rails.application.config.paths["db"].first, "schema_cache.dump")
@@ -385,7 +261,7 @@ db_namespace = namespace :db do
end
desc 'Clear a db/schema_cache.dump file.'
- task :clear => :environment do
+ task :clear => [:environment, :load_config] do
filename = File.join(Rails.application.config.paths["db"].first, "schema_cache.dump")
FileUtils.rm(filename) if File.exists?(filename)
end
@@ -395,25 +271,15 @@ db_namespace = namespace :db do
namespace :structure do
desc 'Dump the database structure to db/structure.sql. Specify another file with DB_STRUCTURE=db/my_structure.sql'
- task :dump => :environment do
+ task :dump => [:environment, :load_config] do
abcs = ActiveRecord::Base.configurations
filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql")
case abcs[Rails.env]['adapter']
- when /mysql/, 'oci', 'oracle'
+ when /mysql/, /postgresql/, /sqlite/
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(abcs[Rails.env], filename)
+ when 'oci', 'oracle'
ActiveRecord::Base.establish_connection(abcs[Rails.env])
File.open(filename, "w:utf-8") { |f| f << ActiveRecord::Base.connection.structure_dump }
- when /postgresql/
- set_psql_env(abcs[Rails.env])
- search_path = abcs[Rails.env]['schema_search_path']
- unless search_path.blank?
- search_path = search_path.split(",").map{|search_path_part| "--schema=#{Shellwords.escape(search_path_part.strip)}" }.join(" ")
- end
- `pg_dump -i -s -x -O -f #{Shellwords.escape(filename)} #{search_path} #{Shellwords.escape(abcs[Rails.env]['database'])}`
- raise 'Error dumping database' if $?.exitstatus == 1
- File.open(filename, "a") { |f| f << "SET search_path TO #{ActiveRecord::Base.connection.schema_search_path};\n\n" }
- when /sqlite/
- dbfile = abcs[Rails.env]['database']
- `sqlite3 #{dbfile} .schema > #{filename}`
when 'sqlserver'
`smoscript -s #{abcs[Rails.env]['host']} -d #{abcs[Rails.env]['database']} -u #{abcs[Rails.env]['username']} -p #{abcs[Rails.env]['password']} -f #{filename} -A -U`
when "firebird"
@@ -437,18 +303,8 @@ db_namespace = namespace :db do
abcs = ActiveRecord::Base.configurations
filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql")
case abcs[env]['adapter']
- when /mysql/
- ActiveRecord::Base.establish_connection(abcs[env])
- ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0')
- IO.read(filename).split("\n\n").each do |table|
- ActiveRecord::Base.connection.execute(table)
- end
- when /postgresql/
- set_psql_env(abcs[env])
- `psql -f "#{filename}" #{abcs[env]['database']}`
- when /sqlite/
- dbfile = abcs[env]['database']
- `sqlite3 #{dbfile} < "#{filename}"`
+ when /mysql/, /postgresql/, /sqlite/
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(abcs[Rails.env], filename)
when 'sqlserver'
`sqlcmd -S #{abcs[env]['host']} -d #{abcs[env]['database']} -U #{abcs[env]['username']} -P #{abcs[env]['password']} -i #{filename}`
when 'oci', 'oracle'
@@ -506,19 +362,11 @@ db_namespace = namespace :db do
task :clone_structure => [ "db:structure:dump", "db:test:load_structure" ]
# desc "Empty the test database"
- task :purge => :environment do
+ task :purge => [:environment, :load_config] do
abcs = ActiveRecord::Base.configurations
case abcs['test']['adapter']
- when /mysql/
- ActiveRecord::Base.establish_connection(:test)
- ActiveRecord::Base.connection.recreate_database(abcs['test']['database'], mysql_creation_options(abcs['test']))
- when /postgresql/
- ActiveRecord::Base.clear_active_connections!
- drop_database(abcs['test'])
- create_database(abcs['test'])
- when /sqlite/
- dbfile = abcs['test']['database']
- File.delete(dbfile) if File.exist?(dbfile)
+ when /mysql/, /postgresql/, /sqlite/
+ ActiveRecord::Tasks::DatabaseTasks.purge abcs['test']
when 'sqlserver'
test = abcs.deep_dup['test']
test_database = test['database']
@@ -548,7 +396,7 @@ db_namespace = namespace :db do
namespace :sessions do
# desc "Creates a sessions migration for use with ActiveRecord::SessionStore"
- task :create => :environment do
+ task :create => [:environment, :load_config] do
raise 'Task unavailable to this database (no migration support)' unless ActiveRecord::Base.connection.supports_migrations?
Rails.application.load_generators
require 'rails/generators/rails/session_migration/session_migration_generator'
@@ -556,7 +404,7 @@ db_namespace = namespace :db do
end
# desc "Clear the sessions table"
- task :clear => :environment do
+ task :clear => [:environment, :load_config] do
ActiveRecord::Base.connection.execute "DELETE FROM #{session_table_name}"
end
end
@@ -592,37 +440,6 @@ end
task 'test:prepare' => 'db:test:prepare'
-def drop_database(config)
- case config['adapter']
- when /mysql/
- ActiveRecord::Base.establish_connection(config)
- ActiveRecord::Base.connection.drop_database config['database']
- when /sqlite/
- require 'pathname'
- path = Pathname.new(config['database'])
- file = path.absolute? ? path.to_s : File.join(Rails.root, path)
-
- FileUtils.rm(file)
- when /postgresql/
- ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
- ActiveRecord::Base.connection.drop_database config['database']
- end
-end
-
-def drop_database_and_rescue(config)
- begin
- drop_database(config)
- rescue Exception => e
- $stderr.puts "Couldn't drop #{config['database']} : #{e.inspect}"
- end
-end
-
-def configs_for_environment
- environments = [Rails.env]
- environments << 'test' if Rails.env.development?
- ActiveRecord::Base.configurations.values_at(*environments).compact.reject { |config| config['database'].blank? }
-end
-
def session_table_name
ActiveRecord::SessionStore::Session.table_name
end
@@ -635,10 +452,3 @@ end
def firebird_db_string(config)
FireRuby::Database.db_string_for(config.symbolize_keys)
end
-
-def set_psql_env(config)
- ENV['PGHOST'] = config['host'] if config['host']
- ENV['PGPORT'] = config['port'].to_s if config['port']
- ENV['PGPASSWORD'] = config['password'].to_s if config['password']
- ENV['PGUSER'] = config['username'].to_s if config['username']
-end
diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb
index 836b15e2ce..960b78dc38 100644
--- a/activerecord/lib/active_record/readonly_attributes.rb
+++ b/activerecord/lib/active_record/readonly_attributes.rb
@@ -6,7 +6,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- config_attribute :_attr_readonly
+ class_attribute :_attr_readonly, instance_writer: false
self._attr_readonly = []
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index c380b5c029..0d9534acd6 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -7,8 +7,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- extend ActiveModel::Configuration
- config_attribute :reflections
+ class_attribute :reflections
self.reflections = {}
end
@@ -18,36 +17,17 @@ module ActiveRecord
# and creates input fields for all of the attributes depending on their type
# and displays the associations to other objects.
#
- # MacroReflection class has info for AggregateReflection and AssociationReflection
- # classes.
+ # MacroReflection class has info for the AssociationReflection
+ # class.
module ClassMethods
def create_reflection(macro, name, options, active_record)
- case macro
- when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
- klass = options[:through] ? ThroughReflection : AssociationReflection
- reflection = klass.new(macro, name, options, active_record)
- when :composed_of
- reflection = AggregateReflection.new(macro, name, options, active_record)
- end
+ klass = options[:through] ? ThroughReflection : AssociationReflection
+ reflection = klass.new(macro, name, options, active_record)
self.reflections = self.reflections.merge(name => reflection)
reflection
end
- # Returns an array of AggregateReflection objects for all the aggregations in the class.
- def reflect_on_all_aggregations
- reflections.values.grep(AggregateReflection)
- end
-
- # Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
- #
- # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
- #
- def reflect_on_aggregation(aggregation)
- reflection = reflections[aggregation]
- reflection if reflection.is_a?(AggregateReflection)
- end
-
# 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>,
@@ -79,24 +59,20 @@ module ActiveRecord
end
end
- # Abstract base class for AggregateReflection and AssociationReflection. Objects of
- # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
+ # Abstract base class for AssociationReflection. Objects of AssociationReflection are returned by the Reflection::ClassMethods.
class MacroReflection
# 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.
#
- # <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.
#
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>{ :class_name => "Money" }</tt>
# <tt>has_many :clients</tt> returns +{}+
attr_reader :options
@@ -115,7 +91,6 @@ module ActiveRecord
# 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
@@ -123,7 +98,6 @@ module ActiveRecord
# 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).to_s
@@ -149,16 +123,6 @@ module ActiveRecord
end
end
-
- # Holds all the meta-data about an aggregation as it was specified in the
- # Active Record class.
- class AggregateReflection < MacroReflection #:nodoc:
- def mapping
- mapping = options[:mapping] || [name, name]
- mapping.first.is_a?(Array) ? mapping : [mapping]
- end
- end
-
# Holds all the meta-data about an association as it was specified in the
# Active Record class.
class AssociationReflection < MacroReflection #:nodoc:
@@ -197,6 +161,10 @@ module ActiveRecord
@quoted_table_name ||= klass.quoted_table_name
end
+ def join_table
+ @join_table ||= options[:join_table] || derive_join_table
+ end
+
def foreign_key
@foreign_key ||= options[:foreign_key] || derive_foreign_key
end
@@ -244,6 +212,10 @@ module ActiveRecord
def check_validity!
check_validity_of_inverse!
+
+ if has_and_belongs_to_many? && association_foreign_key == foreign_key
+ raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(self)
+ end
end
def check_validity_of_inverse!
@@ -326,6 +298,10 @@ module ActiveRecord
macro == :belongs_to
end
+ def has_and_belongs_to_many?
+ macro == :has_and_belongs_to_many
+ end
+
def association_class
case macro
when :belongs_to
@@ -368,6 +344,10 @@ module ActiveRecord
end
end
+ def derive_join_table
+ [active_record.table_name, klass.table_name].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_")
+ end
+
def primary_key(klass)
klass.primary_key || raise(UnknownPrimaryKey.new(klass))
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 05ced3299b..fe3aa00a74 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -300,7 +300,7 @@ module ActiveRecord
# Person.update(people.keys, people.values)
def update(id, attributes)
if id.is_a?(Array)
- id.each.with_index.map {|one_id, idx| update(one_id, attributes[idx])}
+ id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
else
object = find(id)
object.update_attributes(attributes)
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 54c93332bb..86eb8f35b5 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -107,7 +107,7 @@ module ActiveRecord
relation = with_default_scope
if relation.equal?(self)
- if eager_loading? || (includes_values.present? && references_eager_loaded_tables?)
+ if has_include?(column_name)
construct_relation_for_association_calculations.calculate(operation, column_name, options)
else
perform_calculation(operation, column_name, options)
@@ -138,6 +138,10 @@ module ActiveRecord
# # SELECT people.id FROM people
# # => [1, 2, 3]
#
+ # Person.pluck(:id, :name)
+ # # SELECT people.id, people.name FROM people
+ # # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
+ #
# Person.uniq.pluck(:role)
# # SELECT DISTINCT role FROM people
# # => ['admin', 'member', 'guest']
@@ -150,26 +154,35 @@ module ActiveRecord
# # SELECT DATEDIFF(updated_at, created_at) FROM people
# # => ['0', '27761', '173']
#
- def pluck(column_name)
- if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s)
- column_name = "#{table_name}.#{column_name}"
+ def pluck(*column_names)
+ column_names.map! do |column_name|
+ if column_name.is_a?(Symbol) && self.column_names.include?(column_name.to_s)
+ "#{table_name}.#{column_name}"
+ else
+ column_name
+ end
end
- result = klass.connection.select_all(select(column_name).arel, nil, bind_values)
-
- key = result.columns.first
- column = klass.column_types.fetch(key) {
- result.column_types.fetch(key) {
- Class.new { def type_cast(v); v; end }.new
- }
- }
-
- result.map do |attributes|
- raise ArgumentError, "Pluck expects to select just one attribute: #{attributes.inspect}" unless attributes.one?
+ if has_include?(column_names.first)
+ construct_relation_for_association_calculations.pluck(*column_names)
+ else
+ result = klass.connection.select_all(select(column_names).arel, nil, bind_values)
+ columns = result.columns.map do |key|
+ klass.column_types.fetch(key) {
+ result.column_types.fetch(key) {
+ Class.new { def type_cast(v); v; end }.new
+ }
+ }
+ end
- value = klass.initialize_attributes(attributes).values.first
+ result = result.map do |attributes|
+ values = klass.initialize_attributes(attributes).values
- column.type_cast(value)
+ columns.zip(values).map do |column, value|
+ column.type_cast(value)
+ end
+ end
+ columns.one? ? result.map!(&:first) : result
end
end
@@ -185,6 +198,10 @@ module ActiveRecord
private
+ def has_include?(column_name)
+ eager_loading? || (includes_values.present? && (column_name || references_eager_loaded_tables?))
+ end
+
def perform_calculation(operation, column_name, options = {})
operation = operation.to_s.downcase
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 6fa64b39c3..5e5aca0396 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -7,34 +7,36 @@ module ActiveRecord
Relation::MULTI_VALUE_METHODS.each do |name|
class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}_values # def select_values
- @values[:#{name}] || [] # @values[:select] || []
- end # end
- #
- def #{name}_values=(values) # def select_values=(values)
- @values[:#{name}] = values # @values[:select] = values
- end # end
+ def #{name}_values # def select_values
+ @values[:#{name}] || [] # @values[:select] || []
+ end # end
+ #
+ def #{name}_values=(values) # def select_values=(values)
+ raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
+ @values[:#{name}] = values # @values[:select] = values
+ end # end
CODE
end
(Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |name|
class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}_value # def readonly_value
- @values[:#{name}] # @values[:readonly]
- end # end
- #
- def #{name}_value=(value) # def readonly_value=(value)
- @values[:#{name}] = value # @values[:readonly] = value
- end # end
+ def #{name}_value # def readonly_value
+ @values[:#{name}] # @values[:readonly]
+ end # end
CODE
end
- def create_with_value
- @values[:create_with] || {}
+ Relation::SINGLE_VALUE_METHODS.each do |name|
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name}_value=(value) # def readonly_value=(value)
+ raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
+ @values[:#{name}] = value # @values[:readonly] = value
+ end # end
+ CODE
end
- def create_with_value=(value)
- @values[:create_with] = value
+ def create_with_value
+ @values[:create_with] || {}
end
alias extensions extending_values
@@ -191,10 +193,102 @@ module ActiveRecord
self
end
+ # Returns a new relation, which is the result of filtering the current relation
+ # according to the conditions in the arguments.
+ #
+ # #where accepts conditions in one of several formats. In the examples below, the resulting
+ # SQL is given as an illustration; the actual query generated may be different depending
+ # on the database adapter.
+ #
+ # === string
+ #
+ # A single string, without additional arguments, is passed to the query
+ # constructor as a SQL fragment, and used in the where clause of the query.
+ #
+ # Client.where("orders_count = '2'")
+ # # SELECT * from clients where orders_count = '2';
+ #
+ # Note that building your own string from user input may expose your application
+ # to injection attacks if not done properly. As an alternative, it is recommended
+ # to use one of the following methods.
+ #
+ # === array
+ #
+ # If an array is passed, then the first element of the array is treated as a template, and
+ # the remaining elements are inserted into the template to generate the condition.
+ # Active Record takes care of building the query to avoid injection attacks, and will
+ # convert from the ruby type to the database type where needed. Elements are inserted
+ # into the string in the order in which they appear.
+ #
+ # User.where(["name = ? and email = ?", "Joe", "joe@example.com"])
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
+ #
+ # Alternatively, you can use named placeholders in the template, and pass a hash as the
+ # second element of the array. The names in the template are replaced with the corresponding
+ # values from the hash.
+ #
+ # User.where(["name = :name and email = :email", { name: "Joe", email: "joe@example.com" }])
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
+ #
+ # This can make for more readable code in complex queries.
+ #
+ # Lastly, you can use sprintf-style % escapes in the template. This works slightly differently
+ # than the previous methods; you are responsible for ensuring that the values in the template
+ # are properly quoted. The values are passed to the connector for quoting, but the caller
+ # is responsible for ensuring they are enclosed in quotes in the resulting SQL. After quoting,
+ # the values are inserted using the same escapes as the Ruby core method <tt>Kernel::sprintf</tt>.
+ #
+ # User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"])
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
+ #
+ # If #where is called with multiple arguments, these are treated as if they were passed as
+ # the elements of a single array.
+ #
+ # User.where("name = :name and email = :email", { name: "Joe", email: "joe@example.com" })
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
+ #
+ # When using strings to specify conditions, you can use any operator available from
+ # the database. While this provides the most flexibility, you can also unintentionally introduce
+ # dependencies on the underlying database. If your code is intended for general consumption,
+ # test with multiple database backends.
+ #
+ # === hash
+ #
+ # #where will also accept a hash condition, in which the keys are fields and the values
+ # are values to be searched for.
+ #
+ # Fields can be symbols or strings. Values can be single values, arrays, or ranges.
+ #
+ # User.where({ name: "Joe", email: "joe@example.com" })
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
+ #
+ # User.where({ name: ["Alice", "Bob"]})
+ # # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
+ #
+ # User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
+ # # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
+ #
+ # === Joins
+ #
+ # If the relation is the result of a join, you may create a condition which uses any of the
+ # tables in the join. For string and array conditions, use the table name in the condition.
+ #
+ # User.joins(:posts).where("posts.created_at < ?", Time.now)
+ #
+ # For hash conditions, you can either use the table name in the key, or use a sub-hash.
+ #
+ # User.joins(:posts).where({ "posts.published" => true })
+ # User.joins(:posts).where({ :posts => { :published => true } })
+ #
+ # === empty condition
+ #
+ # If the condition returns true for blank?, then where is a no-op and returns the current relation.
def where(opts, *rest)
opts.blank? ? self : spawn.where!(opts, *rest)
end
+ # #where! is identical to #where, except that instead of returning a new relation, it adds
+ # the condition to the existing relation.
def where!(opts, *rest)
references!(PredicateBuilder.references(opts)) if Hash === opts
@@ -482,8 +576,7 @@ module ActiveRecord
when String, Array
[@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
when Hash
- attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
- PredicateBuilder.build_from_hash(table.engine, attributes, table)
+ PredicateBuilder.build_from_hash(table.engine, opts, table)
else
[opts]
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 5530be3219..46f6c283e3 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -43,36 +43,6 @@ module ActiveRecord
end
end
- # Accepts a hash of SQL conditions and replaces those attributes
- # that correspond to a +composed_of+ relationship with their expanded
- # aggregate attribute values.
- # Given:
- # class Person < ActiveRecord::Base
- # composed_of :address, :class_name => "Address",
- # :mapping => [%w(address_street street), %w(address_city city)]
- # end
- # Then:
- # { :address => Address.new("813 abc st.", "chicago") }
- # # => { :address_street => "813 abc st.", :address_city => "chicago" }
- def expand_hash_conditions_for_aggregates(attrs)
- expanded_attrs = {}
- attrs.each do |attr, value|
- if aggregation = reflect_on_aggregation(attr.to_sym)
- mapping = aggregation.mapping
- mapping.each do |field_attr, aggregate_attr|
- if mapping.size == 1 && !value.respond_to?(aggregate_attr)
- expanded_attrs[field_attr] = value
- else
- expanded_attrs[field_attr] = value.send(aggregate_attr)
- end
- end
- else
- expanded_attrs[attr] = value
- end
- end
- expanded_attrs
- end
-
# Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
# { :name => "foo'bar", :group_id => 4 }
# # => "name='foo''bar' and group_id= 4"
@@ -88,8 +58,6 @@ module ActiveRecord
# { :address => Address.new("123 abc st.", "chicago") }
# # => "address_street='123 abc st.' and address_city='chicago'"
def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
- attrs = expand_hash_conditions_for_aggregates(attrs)
-
table = Arel::Table.new(table_name).alias(default_table_name)
PredicateBuilder.build_from_hash(arel_engine, attrs, table).map { |b|
connection.visitor.accept b
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 1cdaa516ba..a25a8d79bd 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -70,8 +70,8 @@ HEADER
@connection.tables.sort.each do |tbl|
next if ['schema_migrations', ignore_tables].flatten.any? do |ignored|
case ignored
- when String; tbl == ignored
- when Regexp; tbl =~ ignored
+ when String; remove_prefix_and_suffix(tbl) == ignored
+ when Regexp; remove_prefix_and_suffix(tbl) =~ ignored
else
raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
end
@@ -92,7 +92,7 @@ HEADER
pk = @connection.primary_key(table)
end
- tbl.print " create_table #{table.inspect}"
+ tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
if columns.detect { |c| c.name == pk }
if pk != 'id'
tbl.print %Q(, :primary_key => "#{pk}")
@@ -185,7 +185,7 @@ HEADER
if (indexes = @connection.indexes(table)).any?
add_index_statements = indexes.map do |index|
statement_parts = [
- ('add_index ' + index.table.inspect),
+ ('add_index ' + remove_prefix_and_suffix(index.table).inspect),
index.columns.inspect,
(':name => ' + index.name.inspect),
]
@@ -206,5 +206,9 @@ HEADER
stream.puts
end
end
+
+ def remove_prefix_and_suffix(table)
+ table.gsub(/^(#{ActiveRecord::Base.table_name_prefix})(.+)(#{ActiveRecord::Base.table_name_suffix})$/, "\\2")
+ end
end
end
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index db833fc7f1..af51c803a7 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -8,7 +8,7 @@ module ActiveRecord
included do
# Stores the default scope for the class
- config_attribute :default_scopes
+ class_attribute :default_scopes, instance_writer: false
self.default_scopes = []
end
diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb
index 41e3b92499..e8dd312a47 100644
--- a/activerecord/lib/active_record/serialization.rb
+++ b/activerecord/lib/active_record/serialization.rb
@@ -1,9 +1,21 @@
module ActiveRecord #:nodoc:
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :include_root_in_json, instance_accessor: false
+ self.include_root_in_json = true
+ end
+
# = Active Record Serialization
module Serialization
extend ActiveSupport::Concern
include ActiveModel::Serializers::JSON
+ included do
+ singleton_class.class_eval do
+ remove_method :include_root_in_json
+ delegate :include_root_in_json, to: 'ActiveRecord::Model'
+ end
+ end
+
def serializable_hash(options = nil)
options = options.try(:clone) || {}
diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb
index b833af64fe..834d01a1e8 100644
--- a/activerecord/lib/active_record/serializers/xml_serializer.rb
+++ b/activerecord/lib/active_record/serializers/xml_serializer.rb
@@ -177,11 +177,6 @@ module ActiveRecord #:nodoc:
end
class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc:
- def initialize(*args)
- super
- options[:except] = Array(options[:except]) | Array(@serializable.class.inheritance_column)
- end
-
class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:
def compute_type
klass = @serializable.class
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index 542cb3187a..d13491502e 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -1,4 +1,6 @@
+require 'active_support/concern'
require 'active_support/core_ext/hash/indifferent_access'
+require 'active_support/core_ext/class/attribute'
module ActiveRecord
# Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column.
@@ -33,9 +35,18 @@ module ActiveRecord
# class SuperUser < User
# store_accessor :settings, :privileges, :servants
# end
+ #
+ # The stored attribute names can be retrieved using +stored_attributes+.
+ #
+ # User.stored_attributes[:settings] # [:color, :homepage]
module Store
extend ActiveSupport::Concern
+ included do
+ class_attribute :stored_attributes
+ self.stored_attributes = {}
+ end
+
module ClassMethods
def store(store_attribute, options = {})
serialize store_attribute, IndifferentCoder.new(options[:coder])
@@ -43,7 +54,8 @@ module ActiveRecord
end
def store_accessor(store_attribute, *keys)
- keys.flatten.each do |key|
+ keys = keys.flatten
+ keys.each do |key|
define_method("#{key}=") do |value|
initialize_store_attribute(store_attribute)
send(store_attribute)[key] = value
@@ -55,6 +67,8 @@ module ActiveRecord
send(store_attribute)[key]
end
end
+
+ self.stored_attributes[store_attribute] = keys
end
end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
new file mode 100644
index 0000000000..999b2ebc85
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -0,0 +1,109 @@
+module ActiveRecord
+ module Tasks # :nodoc:
+ module DatabaseTasks # :nodoc:
+ extend self
+
+ TASKS_PATTERNS = {
+ /mysql/ => ActiveRecord::Tasks::MySQLDatabaseTasks,
+ /postgresql/ => ActiveRecord::Tasks::PostgreSQLDatabaseTasks,
+ /sqlite/ => ActiveRecord::Tasks::SQLiteDatabaseTasks
+ }
+ LOCAL_HOSTS = ['127.0.0.1', 'localhost']
+
+ def create(*arguments)
+ configuration = arguments.first
+ class_for_adapter(configuration['adapter']).new(*arguments).create
+ rescue Exception => error
+ $stderr.puts error, *(error.backtrace)
+ $stderr.puts "Couldn't create database for #{configuration.inspect}"
+ end
+
+ def create_all
+ each_local_configuration { |configuration| create configuration }
+ end
+
+ def create_current(environment = Rails.env)
+ each_current_configuration(environment) { |configuration|
+ create configuration
+ }
+ ActiveRecord::Base.establish_connection environment
+ end
+
+ def drop(*arguments)
+ configuration = arguments.first
+ class_for_adapter(configuration['adapter']).new(*arguments).drop
+ rescue Exception => error
+ $stderr.puts error, *(error.backtrace)
+ $stderr.puts "Couldn't drop #{configuration['database']}"
+ end
+
+ def drop_all
+ each_local_configuration { |configuration| drop configuration }
+ end
+
+ def drop_current(environment = Rails.env)
+ each_current_configuration(environment) { |configuration|
+ drop configuration
+ }
+ end
+
+ def charset_current(environment = Rails.env)
+ charset ActiveRecord::Base.configurations[environment]
+ end
+
+ def charset(*arguments)
+ configuration = arguments.first
+ class_for_adapter(configuration['adapter']).new(*arguments).charset
+ end
+
+ def purge(configuration)
+ class_for_adapter(configuration['adapter']).new(configuration).purge
+ end
+
+ def structure_dump(*arguments)
+ configuration = arguments.first
+ filename = arguments.delete_at 1
+ class_for_adapter(configuration['adapter']).new(*arguments).structure_dump(filename)
+ end
+
+ def structure_load(*arguments)
+ configuration = arguments.first
+ filename = arguments.delete_at 1
+ class_for_adapter(configuration['adapter']).new(*arguments).structure_load(filename)
+ end
+
+ private
+
+ def class_for_adapter(adapter)
+ key = TASKS_PATTERNS.keys.detect { |pattern| adapter[pattern] }
+ TASKS_PATTERNS[key]
+ end
+
+ def each_current_configuration(environment)
+ environments = [environment]
+ environments << 'test' if environment.development?
+
+ configurations = ActiveRecord::Base.configurations.values_at(*environments)
+ configurations.compact.each do |configuration|
+ yield configuration unless configuration['database'].blank?
+ end
+ end
+
+ def each_local_configuration
+ ActiveRecord::Base.configurations.each_value do |configuration|
+ next unless configuration['database']
+
+ if local_database?(configuration)
+ yield configuration
+ else
+ $stderr.puts "This task only modifies local databases. #{configuration['database']} is on a remote host."
+ end
+ end
+ end
+
+ def local_database?(configuration)
+ configuration['host'].in?(LOCAL_HOSTS) || configuration['host'].blank?
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
new file mode 100644
index 0000000000..b39cd2282f
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -0,0 +1,110 @@
+module ActiveRecord
+ module Tasks # :nodoc:
+ class MySQLDatabaseTasks # :nodoc:
+
+ DEFAULT_CHARSET = ENV['CHARSET'] || 'utf8'
+ DEFAULT_COLLATION = ENV['COLLATION'] || 'utf8_unicode_ci'
+ ACCESS_DENIED_ERROR = 1045
+
+ delegate :connection, :establish_connection, to: ActiveRecord::Base
+
+ def initialize(configuration)
+ @configuration = configuration
+ end
+
+ def create
+ establish_connection configuration_without_database
+ connection.create_database configuration['database'], creation_options
+ establish_connection configuration
+ rescue error_class => error
+ raise error unless error.errno == ACCESS_DENIED_ERROR
+
+ $stdout.print error.error
+ establish_connection root_configuration_without_database
+ connection.create_database configuration['database'], creation_options
+ connection.execute grant_statement.gsub(/\s+/, ' ').strip
+ establish_connection configuration
+ rescue error_class => error
+ $stderr.puts error.error
+ $stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}"
+ $stderr.puts "(If you set the charset manually, make sure you have a matching collation)" if configuration['charset']
+ end
+
+ def drop
+ establish_connection configuration
+ connection.drop_database configuration['database']
+ end
+
+ def purge
+ establish_connection :test
+ connection.recreate_database configuration['database'], creation_options
+ end
+
+ def charset
+ connection.charset
+ end
+
+ def structure_dump(filename)
+ establish_connection configuration
+ File.open(filename, "w:utf-8") { |f| f << ActiveRecord::Base.connection.structure_dump }
+ end
+
+ def structure_load(filename)
+ establish_connection(configuration)
+ connection.execute('SET foreign_key_checks = 0')
+ IO.read(filename).split("\n\n").each do |table|
+ connection.execute(table)
+ end
+ end
+
+ private
+
+ def configuration
+ @configuration
+ end
+
+ def configuration_without_database
+ configuration.merge('database' => nil)
+ end
+
+ def creation_options
+ {
+ charset: (configuration['charset'] || DEFAULT_CHARSET),
+ collation: (configuration['collation'] || DEFAULT_COLLATION)
+ }
+ end
+
+ def error_class
+ case configuration['adapter']
+ when /jdbc/
+ require 'active_record/railties/jdbcmysql_error'
+ ArJdbcMySQL::Error
+ when /mysql2/
+ Mysql2::Error
+ else
+ Mysql::Error
+ end
+ end
+
+ def grant_statement
+ <<-SQL
+GRANT ALL PRIVILEGES ON #{configuration['database']}.*
+ TO '#{configuration['username']}'@'localhost'
+IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
+ SQL
+ end
+
+ def root_configuration_without_database
+ configuration_without_database.merge(
+ 'username' => 'root',
+ 'password' => root_password
+ )
+ end
+
+ def root_password
+ $stdout.print "Please provide the root password for your mysql installation\n>"
+ $stdin.gets.strip
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
new file mode 100644
index 0000000000..a210392e53
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -0,0 +1,81 @@
+require 'shellwords'
+
+module ActiveRecord
+ module Tasks # :nodoc:
+ class PostgreSQLDatabaseTasks # :nodoc:
+
+ DEFAULT_ENCODING = ENV['CHARSET'] || 'utf8'
+
+ delegate :connection, :establish_connection, :clear_active_connections!,
+ to: ActiveRecord::Base
+
+ def initialize(configuration)
+ @configuration = configuration
+ end
+
+ def create(master_established = false)
+ establish_master_connection unless master_established
+ connection.create_database configuration['database'],
+ configuration.merge('encoding' => encoding)
+ establish_connection configuration
+ end
+
+ def drop
+ establish_master_connection
+ connection.drop_database configuration['database']
+ end
+
+ def charset
+ connection.encoding
+ end
+
+ def purge
+ clear_active_connections!
+ drop
+ create true
+ end
+
+ def structure_dump(filename)
+ set_psql_env
+ search_path = configuration['schema_search_path']
+ unless search_path.blank?
+ search_path = search_path.split(",").map{|search_path_part| "--schema=#{Shellwords.escape(search_path_part.strip)}" }.join(" ")
+ end
+
+ command = "pg_dump -i -s -x -O -f #{Shellwords.escape(filename)} #{search_path} #{Shellwords.escape(configuration['database'])}"
+ raise 'Error dumping database' unless Kernel.system(command)
+
+ File.open(filename, "a") { |f| f << "SET search_path TO #{ActiveRecord::Base.connection.schema_search_path};\n\n" }
+ end
+
+ def structure_load(filename)
+ set_psql_env
+ Kernel.system("psql -f #{filename} #{configuration['database']}")
+ end
+
+ private
+
+ def configuration
+ @configuration
+ end
+
+ def encoding
+ configuration['encoding'] || DEFAULT_ENCODING
+ end
+
+ def establish_master_connection
+ establish_connection configuration.merge(
+ 'database' => 'postgres',
+ 'schema_search_path' => 'public'
+ )
+ end
+
+ def set_psql_env
+ ENV['PGHOST'] = configuration['host'] if configuration['host']
+ ENV['PGPORT'] = configuration['port'].to_s if configuration['port']
+ ENV['PGPASSWORD'] = configuration['password'].to_s if configuration['password']
+ ENV['PGUSER'] = configuration['username'].to_s if configuration['username']
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
new file mode 100644
index 0000000000..da01058a82
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
@@ -0,0 +1,55 @@
+module ActiveRecord
+ module Tasks # :nodoc:
+ class SQLiteDatabaseTasks # :nodoc:
+
+ delegate :connection, :establish_connection, to: ActiveRecord::Base
+
+ def initialize(configuration, root = Rails.root)
+ @configuration, @root = configuration, root
+ end
+
+ def create
+ if File.exist?(configuration['database'])
+ $stderr.puts "#{configuration['database']} already exists"
+ return
+ end
+
+ establish_connection configuration
+ connection
+ end
+
+ def drop
+ require 'pathname'
+ path = Pathname.new configuration['database']
+ file = path.absolute? ? path.to_s : File.join(root, path)
+
+ FileUtils.rm(file) if File.exist?(file)
+ end
+ alias :purge :drop
+
+ def charset
+ connection.encoding
+ end
+
+ def structure_dump(filename)
+ dbfile = configuration['database']
+ `sqlite3 #{dbfile} .schema > #{filename}`
+ end
+
+ def structure_load(filename)
+ dbfile = configuration['database']
+ `sqlite3 #{dbfile} < "#{filename}"`
+ end
+
+ private
+
+ def configuration
+ @configuration
+ end
+
+ def root
+ @root
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index c717fdea47..e5b7a6bfba 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -1,6 +1,11 @@
require 'active_support/core_ext/class/attribute'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :record_timestamps, instance_accessor: false
+ self.record_timestamps = true
+ end
+
# = Active Record Timestamp
#
# Active Record automatically timestamps create and update operations if the
@@ -33,8 +38,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- config_attribute :record_timestamps, :instance_writer => true
- self.record_timestamps = true
+ config_attribute :record_timestamps, instance_writer: true
end
def initialize_dup(other)
diff --git a/activerecord/test/active_record/connection_adapters/fake_adapter.rb b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
index 69dfd2503e..1199be68eb 100644
--- a/activerecord/test/active_record/connection_adapters/fake_adapter.rb
+++ b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
@@ -1,6 +1,6 @@
module ActiveRecord
- class Base
- def self.fake_connection(config)
+ module ConnectionHandling
+ def fake_connection(config)
ConnectionAdapters::FakeAdapter.new nil, logger
end
end
diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
index 6faceaf7c0..8e3eeac81e 100644
--- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
@@ -34,11 +34,11 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
'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'
+ 'distinct_select'=>'distinct_id int, select_id int'
end
def teardown
- drop_tables_directly ['group', 'select', 'values', 'distinct', 'distincts_selects', 'order']
+ drop_tables_directly ['group', 'select', 'values', 'distinct', 'distinct_select', 'order']
end
# create tables with reserved-word names and columns
@@ -80,7 +80,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
#activerecord model class with reserved-word table name
def test_activerecord_model
- create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ create_test_fixtures :select, :distinct, :group, :values, :distinct_select
x = nil
assert_nothing_raised { x = Group.new }
x.order = 'x'
@@ -94,7 +94,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
# has_one association with reserved-word table name
def test_has_one_associations
- create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ create_test_fixtures :select, :distinct, :group, :values, :distinct_select
v = nil
assert_nothing_raised { v = Group.find(1).values }
assert_equal 2, v.id
@@ -102,7 +102,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
# belongs_to association with reserved-word table name
def test_belongs_to_associations
- create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ create_test_fixtures :select, :distinct, :group, :values, :distinct_select
gs = nil
assert_nothing_raised { gs = Select.find(2).groups }
assert_equal gs.length, 2
@@ -111,7 +111,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
# 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
+ create_test_fixtures :select, :distinct, :group, :values, :distinct_select
s = nil
assert_nothing_raised { s = Distinct.find(1).selects }
assert_equal s.length, 2
diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
index 32d4282623..5c2c113c78 100644
--- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
@@ -34,11 +34,11 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
'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'
+ 'distinct_select'=>'distinct_id int, select_id int'
end
def teardown
- drop_tables_directly ['group', 'select', 'values', 'distinct', 'distincts_selects', 'order']
+ drop_tables_directly ['group', 'select', 'values', 'distinct', 'distinct_select', 'order']
end
# create tables with reserved-word names and columns
@@ -80,7 +80,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
#activerecord model class with reserved-word table name
def test_activerecord_model
- create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ create_test_fixtures :select, :distinct, :group, :values, :distinct_select
x = nil
assert_nothing_raised { x = Group.new }
x.order = 'x'
@@ -94,7 +94,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
# has_one association with reserved-word table name
def test_has_one_associations
- create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ create_test_fixtures :select, :distinct, :group, :values, :distinct_select
v = nil
assert_nothing_raised { v = Group.find(1).values }
assert_equal 2, v.id
@@ -102,7 +102,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
# belongs_to association with reserved-word table name
def test_belongs_to_associations
- create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ create_test_fixtures :select, :distinct, :group, :values, :distinct_select
gs = nil
assert_nothing_raised { gs = Select.find(2).groups }
assert_equal gs.length, 2
@@ -111,7 +111,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
# 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
+ create_test_fixtures :select, :distinct, :group, :values, :distinct_select
s = nil
assert_nothing_raised { s = Distinct.find(1).selects }
assert_equal s.length, 2
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index adb2cef010..f6e168caf2 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -21,6 +21,10 @@ module ActiveRecord
assert_not_nil @connection.encoding
end
+ def test_default_client_min_messages
+ assert_equal "warning", @connection.client_min_messages
+ end
+
# Ensure, we can set connection params using the example of Generic
# Query Optimizer (geqo). It is 'on' per default.
def test_connection_options
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index 34660577da..a4d9286d52 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -27,6 +27,9 @@ end
class PostgresqlTimestampWithZone < ActiveRecord::Base
end
+class PostgresqlUUID < ActiveRecord::Base
+end
+
class PostgresqlDataTypeTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
@@ -61,6 +64,9 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
@first_oid = PostgresqlOid.find(1)
@connection.execute("INSERT INTO postgresql_timestamp_with_zones (time) VALUES ('2010-01-01 10:00:00-1')")
+
+ @connection.execute("INSERT INTO postgresql_uuids (guid, compact_guid) VALUES('d96c3da0-96c1-012f-1316-64ce8f32c6d8', 'f06c715096c1012f131764ce8f32c6d8')")
+ @first_uuid = PostgresqlUUID.find(1)
end
def test_data_type_of_array_types
@@ -100,6 +106,10 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal :integer, @first_oid.column_for_attribute(:obj_id).type
end
+ def test_data_type_of_uuid_types
+ assert_equal :uuid, @first_uuid.column_for_attribute(:guid).type
+ end
+
def test_array_values
assert_equal '{35000,21000,18000,17000}', @first_array.commission_by_quarter
assert_equal '{foo,bar,baz}', @first_array.nicknames
@@ -143,6 +153,11 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal '01:23:45:67:89:0a', @first_network_address.mac_address
end
+ def test_uuid_values
+ assert_equal 'd96c3da0-96c1-012f-1316-64ce8f32c6d8', @first_uuid.guid
+ assert_equal 'f06c7150-96c1-012f-1317-64ce8f32c6d8', @first_uuid.compact_guid
+ end
+
def test_bit_string_values
assert_equal '00010101', @first_bit_string.bit_string
assert_equal '00010101', @first_bit_string.bit_string_varying
diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb
deleted file mode 100644
index 5bd8f76ba2..0000000000
--- a/activerecord/test/cases/aggregations_test.rb
+++ /dev/null
@@ -1,158 +0,0 @@
-require "cases/helper"
-require 'models/customer'
-require 'active_support/core_ext/exception'
-
-class AggregationsTest < ActiveRecord::TestCase
- fixtures :customers
-
- def test_find_single_value_object
- assert_equal 50, customers(:david).balance.amount
- assert_kind_of Money, customers(:david).balance
- assert_equal 300, customers(:david).balance.exchange_to("DKK").amount
- end
-
- def test_find_multiple_value_object
- assert_equal customers(:david).address_street, customers(:david).address.street
- assert(
- customers(:david).address.close_to?(Address.new("Different Street", customers(:david).address_city, customers(:david).address_country))
- )
- end
-
- def test_change_single_value_object
- customers(:david).balance = Money.new(100)
- customers(:david).save
- assert_equal 100, customers(:david).reload.balance.amount
- end
-
- def test_immutable_value_objects
- customers(:david).balance = Money.new(100)
- assert_raise(ActiveSupport::FrozenObjectError) { customers(:david).balance.instance_eval { @amount = 20 } }
- end
-
- def test_inferred_mapping
- assert_equal "35.544623640962634", customers(:david).gps_location.latitude
- assert_equal "-105.9309951055148", customers(:david).gps_location.longitude
-
- customers(:david).gps_location = GpsLocation.new("39x-110")
-
- assert_equal "39", customers(:david).gps_location.latitude
- assert_equal "-110", customers(:david).gps_location.longitude
-
- customers(:david).save
-
- customers(:david).reload
-
- assert_equal "39", customers(:david).gps_location.latitude
- assert_equal "-110", customers(:david).gps_location.longitude
- end
-
- def test_reloaded_instance_refreshes_aggregations
- assert_equal "35.544623640962634", customers(:david).gps_location.latitude
- assert_equal "-105.9309951055148", customers(:david).gps_location.longitude
-
- Customer.update_all("gps_location = '24x113'")
- customers(:david).reload
- assert_equal '24x113', customers(:david)['gps_location']
-
- assert_equal GpsLocation.new('24x113'), customers(:david).gps_location
- end
-
- def test_gps_equality
- assert_equal GpsLocation.new('39x110'), GpsLocation.new('39x110')
- end
-
- def test_gps_inequality
- assert_not_equal GpsLocation.new('39x110'), GpsLocation.new('39x111')
- end
-
- def test_allow_nil_gps_is_nil
- assert_nil customers(:zaphod).gps_location
- end
-
- def test_allow_nil_gps_set_to_nil
- customers(:david).gps_location = nil
- customers(:david).save
- customers(:david).reload
- assert_nil customers(:david).gps_location
- end
-
- def test_allow_nil_set_address_attributes_to_nil
- customers(:zaphod).address = nil
- assert_nil customers(:zaphod).attributes[:address_street]
- assert_nil customers(:zaphod).attributes[:address_city]
- assert_nil customers(:zaphod).attributes[:address_country]
- end
-
- def test_allow_nil_address_set_to_nil
- customers(:zaphod).address = nil
- customers(:zaphod).save
- customers(:zaphod).reload
- assert_nil customers(:zaphod).address
- end
-
- def test_nil_raises_error_when_allow_nil_is_false
- assert_raise(NoMethodError) { customers(:david).balance = nil }
- end
-
- def test_allow_nil_address_loaded_when_only_some_attributes_are_nil
- customers(:zaphod).address_street = nil
- customers(:zaphod).save
- customers(:zaphod).reload
- assert_kind_of Address, customers(:zaphod).address
- assert_nil customers(:zaphod).address.street
- end
-
- def test_nil_assignment_results_in_nil
- customers(:david).gps_location = GpsLocation.new('39x111')
- assert_not_nil customers(:david).gps_location
- customers(:david).gps_location = nil
- assert_nil customers(:david).gps_location
- end
-
- def test_nil_return_from_converter_is_respected_when_allow_nil_is_true
- customers(:david).non_blank_gps_location = ""
- customers(:david).save
- customers(:david).reload
- assert_nil customers(:david).non_blank_gps_location
- end
-
- def test_nil_return_from_converter_results_in_failure_when_allow_nil_is_false
- assert_raises(NoMethodError) do
- customers(:barney).gps_location = ""
- end
- end
-
- def test_do_not_run_the_converter_when_nil_was_set
- customers(:david).non_blank_gps_location = nil
- assert_nil Customer.gps_conversion_was_run
- end
-
- def test_custom_constructor
- assert_equal 'Barney GUMBLE', customers(:barney).fullname.to_s
- assert_kind_of Fullname, customers(:barney).fullname
- end
-
- def test_custom_converter
- customers(:barney).fullname = 'Barnoit Gumbleau'
- assert_equal 'Barnoit GUMBLEAU', customers(:barney).fullname.to_s
- assert_kind_of Fullname, customers(:barney).fullname
- end
-end
-
-class OverridingAggregationsTest < ActiveRecord::TestCase
- class Name; end
- class DifferentName; end
-
- class Person < ActiveRecord::Base
- composed_of :composed_of, :mapping => %w(person_first_name first_name)
- end
-
- class DifferentPerson < Person
- composed_of :composed_of, :class_name => 'DifferentName', :mapping => %w(different_person_first_name first_name)
- end
-
- def test_composed_of_aggregation_redefinition_reflections_should_differ_and_not_inherited
- assert_not_equal Person.reflect_on_aggregation(:composed_of),
- DifferentPerson.reflect_on_aggregation(:composed_of)
- end
-end
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 9d693bae0c..24bb4adf0a 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
@@ -773,9 +773,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_self_referential_habtm_without_foreign_key_set_should_raise_exception
assert_raise(ActiveRecord::HasAndBelongsToManyAssociationForeignKeyNeeded) {
- Member.class_eval do
- has_and_belongs_to_many :friends, :class_name => "Member", :join_table => "member_friends"
- end
+ SelfMember.new.friends
}
end
diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb
index 98c38535a6..f4c40b8b97 100644
--- a/activerecord/test/cases/attribute_methods/read_test.rb
+++ b/activerecord/test/cases/attribute_methods/read_test.rb
@@ -15,6 +15,7 @@ module ActiveRecord
def self.active_record_super; Base; end
def self.base_class; self; end
+ extend ActiveRecord::Configuration
include ActiveRecord::AttributeMethods
def self.define_attribute_methods
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index f95230ff50..2fee553cef 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -888,22 +888,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
end
- def test_multiparameter_assignment_of_aggregation
- customer = Customer.new
- address = Address.new("The Street", "The City", "The Country")
- attributes = { "address(1)" => address.street, "address(2)" => address.city, "address(3)" => address.country }
- customer.attributes = attributes
- assert_equal address, customer.address
- end
-
- def test_multiparameter_assignment_of_aggregation_out_of_order
- customer = Customer.new
- address = Address.new("The Street", "The City", "The Country")
- attributes = { "address(3)" => address.country, "address(2)" => address.city, "address(1)" => address.street }
- customer.attributes = attributes
- assert_equal address, customer.address
- end
-
def test_multiparameter_assignment_of_aggregation_with_missing_values
ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
customer = Customer.new
@@ -914,14 +898,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal("address", ex.errors[0].attribute)
end
- def test_multiparameter_assignment_of_aggregation_with_blank_values
- customer = Customer.new
- address = Address.new("The Street", "The City", "The Country")
- attributes = { "address(1)" => "", "address(2)" => address.city, "address(3)" => address.country }
- customer.attributes = attributes
- assert_equal Address.new(nil, "The City", "The Country"), customer.address
- end
-
def test_multiparameter_assignment_of_aggregation_with_large_index
ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
customer = Customer.new
@@ -1021,26 +997,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal("c", duped_topic.title)
end
- def test_dup_with_aggregate_of_same_name_as_attribute
- dev = DeveloperWithAggregate.find(1)
- assert_kind_of DeveloperSalary, dev.salary
-
- dup = nil
- assert_nothing_raised { dup = dev.dup }
- assert_kind_of DeveloperSalary, dup.salary
- assert_equal dev.salary.amount, dup.salary.amount
- assert !dup.persisted?
-
- # test if the attributes have been dupd
- original_amount = dup.salary.amount
- dev.salary.amount = 1
- assert_equal original_amount, dup.salary.amount
-
- assert dup.save
- assert dup.persisted?
- assert_not_equal dup.id, dev.id
- end
-
def test_dup_does_not_copy_associations
author = authors(:david)
assert_not_equal [], author.posts
@@ -1523,6 +1479,8 @@ class BasicsTest < ActiveRecord::TestCase
after_seq = Joke.sequence_name
assert_equal before_seq, after_seq unless before_seq.nil? && after_seq.nil?
+ ensure
+ Joke.reset_sequence_name
end
def test_dont_clear_inheritnce_column_when_setting_explicitly
@@ -1935,7 +1893,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_cache_key_format_for_existing_record_with_nil_updated_at
dev = Developer.first
- dev.update_attribute(:updated_at, nil)
+ dev.update_column(:updated_at, nil)
assert_match(/\/#{dev.id}$/, dev.cache_key)
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index a279b0e77c..4df613488a 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -420,6 +420,40 @@ class CalculationsTest < ActiveRecord::TestCase
Account.where("credit_limit > 50").from('accounts').maximum(:credit_limit)
end
+ def test_maximum_with_not_auto_table_name_prefix_if_column_included
+ Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
+
+ # TODO: Investigate why PG isn't being typecast
+ if current_adapter?(:PostgreSQLAdapter)
+ assert_equal "7", Company.includes(:contracts).maximum(:developer_id)
+ else
+ assert_equal 7, Company.includes(:contracts).maximum(:developer_id)
+ end
+ end
+
+ def test_minimum_with_not_auto_table_name_prefix_if_column_included
+ Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
+
+ # TODO: Investigate why PG isn't being typecast
+ if current_adapter?(:PostgreSQLAdapter)
+ assert_equal "7", Company.includes(:contracts).minimum(:developer_id)
+ else
+ assert_equal 7, Company.includes(:contracts).minimum(:developer_id)
+ end
+ end
+
+ def test_sum_with_not_auto_table_name_prefix_if_column_included
+ Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
+
+ # TODO: Investigate why PG isn't being typecast
+ if current_adapter?(:MysqlAdapter) || current_adapter?(:PostgreSQLAdapter)
+ assert_equal "7", Company.includes(:contracts).sum(:developer_id)
+ else
+ assert_equal 7, Company.includes(:contracts).sum(:developer_id)
+ end
+ end
+
+
def test_from_option_with_specified_index
if Edge.connection.adapter_name == 'MySQL' or Edge.connection.adapter_name == 'Mysql2'
assert_equal Edge.count(:all), Edge.from('edges USE INDEX(unique_edge_index)').count(:all)
@@ -477,6 +511,11 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal [c.id], Company.joins(:contracts).pluck(:id)
end
+ def test_pluck_if_table_included
+ c = Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
+ assert_equal [c.id], Company.includes(:contracts).where("contracts.id" => c.contracts.first).pluck(:id)
+ end
+
def test_pluck_not_auto_table_name_prefix_if_column_joined
Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
assert_equal [7], Company.joins(:contracts).pluck(:developer_id)
@@ -493,11 +532,39 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal [50 + 53 + 55 + 60], Account.pluck('SUM(DISTINCT(credit_limit)) as credit_limit')
end
- def test_pluck_expects_a_single_selection
- assert_raise(ArgumentError) { Account.pluck 'id, credit_limit' }
- end
-
def test_plucks_with_ids
assert_equal Company.all.map(&:id).sort, Company.ids.sort
end
+
+ def test_pluck_not_auto_table_name_prefix_if_column_included
+ Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
+ ids = Company.includes(:contracts).pluck(:developer_id)
+ assert_equal Company.count, ids.length
+ assert_equal [7], ids.compact
+ end
+
+ def test_pluck_multiple_columns
+ assert_equal [
+ [1, "The First Topic"], [2, "The Second Topic of the day"],
+ [3, "The Third Topic of the day"], [4, "The Fourth Topic of the day"]
+ ], Topic.order(:id).pluck(:id, :title)
+ assert_equal [
+ [1, "The First Topic", "David"], [2, "The Second Topic of the day", "Mary"],
+ [3, "The Third Topic of the day", "Carl"], [4, "The Fourth Topic of the day", "Carl"]
+ ], Topic.order(:id).pluck(:id, :title, :author_name)
+ end
+
+ def test_pluck_with_multiple_columns_and_selection_clause
+ assert_equal [[1, 50], [2, 50], [3, 50], [4, 60], [5, 55], [6, 53]],
+ Account.pluck('id, credit_limit')
+ end
+
+ def test_pluck_with_multiple_columns_and_includes
+ Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
+ companies_and_developers = Company.order('companies.id').includes(:contracts).pluck(:name, :developer_id)
+
+ assert_equal Company.count, companies_and_developers.length
+ assert_equal ["37signals", nil], companies_and_developers.first
+ assert_equal ["test", 7], companies_and_developers.last
+ end
end
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index a44b49466f..bd2fbaa7db 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -136,12 +136,6 @@ module ActiveRecord
smallint_column = PostgreSQLColumn.new('number', nil, oid, "smallint")
assert_equal :integer, smallint_column.type
end
-
- def test_uuid_column_should_map_to_string
- oid = PostgreSQLAdapter::OID::Identity.new
- uuid_column = PostgreSQLColumn.new('unique_id', nil, oid, "uuid")
- assert_equal :string, uuid_column.type
- end
end
end
end
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index dc99ac665c..17cb447105 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -7,12 +7,11 @@ module ActiveRecord
@handler = ConnectionHandler.new
@handler.establish_connection 'america', Base.connection_pool.spec
@klass = Class.new do
+ include Model::Tag
def self.name; 'america'; end
- class << self
- alias active_record_super superclass
- end
end
@subklass = Class.new(@klass) do
+ include Model::Tag
def self.name; 'north america'; end
end
end
diff --git a/activerecord/test/cases/database_tasks_test.rb b/activerecord/test/cases/database_tasks_test.rb
new file mode 100644
index 0000000000..5f36b2c841
--- /dev/null
+++ b/activerecord/test/cases/database_tasks_test.rb
@@ -0,0 +1,275 @@
+require 'cases/helper'
+
+module ActiveRecord
+ module DatabaseTasksSetupper
+ def setup
+ @mysql_tasks, @postgresql_tasks, @sqlite_tasks = stub, stub, stub
+ ActiveRecord::Tasks::MySQLDatabaseTasks.stubs(:new).returns @mysql_tasks
+ ActiveRecord::Tasks::PostgreSQLDatabaseTasks.stubs(:new).returns @postgresql_tasks
+ ActiveRecord::Tasks::SQLiteDatabaseTasks.stubs(:new).returns @sqlite_tasks
+ end
+ end
+
+ ADAPTERS_TASKS = {
+ :mysql => :mysql_tasks,
+ :mysql2 => :mysql_tasks,
+ :postgresql => :postgresql_tasks,
+ :sqlite3 => :sqlite_tasks
+ }
+
+ class DatabaseTasksCreateTest < ActiveRecord::TestCase
+ include DatabaseTasksSetupper
+
+ ADAPTERS_TASKS.each do |k, v|
+ define_method("test_#{k}_create") do
+ eval("@#{v}").expects(:create)
+ ActiveRecord::Tasks::DatabaseTasks.create 'adapter' => k
+ end
+ end
+ end
+
+ class DatabaseTasksCreateAllTest < ActiveRecord::TestCase
+ def setup
+ @configurations = {'development' => {'database' => 'my-db'}}
+
+ ActiveRecord::Base.stubs(:configurations).returns(@configurations)
+ end
+
+ def test_ignores_configurations_without_databases
+ @configurations['development'].merge!('database' => nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create).never
+
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+
+ def test_ignores_remote_databases
+ @configurations['development'].merge!('host' => 'my.server.tld')
+ $stderr.stubs(:puts).returns(nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create).never
+
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+
+ def test_warning_for_remote_databases
+ @configurations['development'].merge!('host' => 'my.server.tld')
+
+ $stderr.expects(:puts).with('This task only modifies local databases. my-db is on a remote host.')
+
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+
+ def test_creates_configurations_with_local_ip
+ @configurations['development'].merge!('host' => '127.0.0.1')
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create)
+
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+
+ def test_creates_configurations_with_local_host
+ @configurations['development'].merge!('host' => 'localhost')
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create)
+
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+
+ def test_creates_configurations_with_blank_hosts
+ @configurations['development'].merge!('host' => nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create)
+
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+ end
+
+ class DatabaseTasksCreateCurrentTest < ActiveRecord::TestCase
+ def setup
+ @configurations = {
+ 'development' => {'database' => 'dev-db'},
+ 'test' => {'database' => 'test-db'},
+ 'production' => {'database' => 'prod-db'}
+ }
+
+ ActiveRecord::Base.stubs(:configurations).returns(@configurations)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_creates_current_environment_database
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create).
+ with('database' => 'prod-db')
+
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new('production')
+ )
+ end
+
+ def test_creates_test_database_when_environment_is_database
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create).
+ with('database' => 'dev-db')
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create).
+ with('database' => 'test-db')
+
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new('development')
+ )
+ end
+
+ def test_establishes_connection_for_the_given_environment
+ ActiveRecord::Tasks::DatabaseTasks.stubs(:create).returns true
+
+ ActiveRecord::Base.expects(:establish_connection).with('development')
+
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new('development')
+ )
+ end
+ end
+
+ class DatabaseTasksDropTest < ActiveRecord::TestCase
+ include DatabaseTasksSetupper
+
+ ADAPTERS_TASKS.each do |k, v|
+ define_method("test_#{k}_drop") do
+ eval("@#{v}").expects(:drop)
+ ActiveRecord::Tasks::DatabaseTasks.drop 'adapter' => k
+ end
+ end
+ end
+
+ class DatabaseTasksDropAllTest < ActiveRecord::TestCase
+ def setup
+ @configurations = {:development => {'database' => 'my-db'}}
+
+ ActiveRecord::Base.stubs(:configurations).returns(@configurations)
+ end
+
+ def test_ignores_configurations_without_databases
+ @configurations[:development].merge!('database' => nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop).never
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+
+ def test_ignores_remote_databases
+ @configurations[:development].merge!('host' => 'my.server.tld')
+ $stderr.stubs(:puts).returns(nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop).never
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+
+ def test_warning_for_remote_databases
+ @configurations[:development].merge!('host' => 'my.server.tld')
+
+ $stderr.expects(:puts).with('This task only modifies local databases. my-db is on a remote host.')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+
+ def test_creates_configurations_with_local_ip
+ @configurations[:development].merge!('host' => '127.0.0.1')
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop)
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+
+ def test_creates_configurations_with_local_host
+ @configurations[:development].merge!('host' => 'localhost')
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop)
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+
+ def test_creates_configurations_with_blank_hosts
+ @configurations[:development].merge!('host' => nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop)
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+ end
+
+ class DatabaseTasksDropCurrentTest < ActiveRecord::TestCase
+ def setup
+ @configurations = {
+ 'development' => {'database' => 'dev-db'},
+ 'test' => {'database' => 'test-db'},
+ 'production' => {'database' => 'prod-db'}
+ }
+
+ ActiveRecord::Base.stubs(:configurations).returns(@configurations)
+ end
+
+ def test_creates_current_environment_database
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
+ with('database' => 'prod-db')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new('production')
+ )
+ end
+
+ def test_creates_test_database_when_environment_is_database
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
+ with('database' => 'dev-db')
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
+ with('database' => 'test-db')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new('development')
+ )
+ end
+ end
+
+
+ class DatabaseTasksPurgeTest < ActiveRecord::TestCase
+ include DatabaseTasksSetupper
+
+ ADAPTERS_TASKS.each do |k, v|
+ define_method("test_#{k}_purge") do
+ eval("@#{v}").expects(:purge)
+ ActiveRecord::Tasks::DatabaseTasks.purge 'adapter' => k
+ end
+ end
+ end
+
+ class DatabaseTasksCharsetTest < ActiveRecord::TestCase
+ include DatabaseTasksSetupper
+
+ ADAPTERS_TASKS.each do |k, v|
+ define_method("test_#{k}_charset") do
+ eval("@#{v}").expects(:charset)
+ ActiveRecord::Tasks::DatabaseTasks.charset 'adapter' => k
+ end
+ end
+ end
+
+ class DatabaseTasksStructureDumpTest < ActiveRecord::TestCase
+ include DatabaseTasksSetupper
+
+ ADAPTERS_TASKS.each do |k, v|
+ define_method("test_#{k}_structure_dump") do
+ eval("@#{v}").expects(:structure_dump).with("awesome-file.sql")
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump({'adapter' => k}, "awesome-file.sql")
+ end
+ end
+ end
+
+ class DatabaseTasksStructureLoadTest < ActiveRecord::TestCase
+ include DatabaseTasksSetupper
+
+ ADAPTERS_TASKS.each do |k, v|
+ define_method("test_#{k}_structure_load") do
+ eval("@#{v}").expects(:structure_load).with("awesome-file.sql")
+ ActiveRecord::Tasks::DatabaseTasks.structure_load({'adapter' => k}, "awesome-file.sql")
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/deprecated_dynamic_methods_test.rb b/activerecord/test/cases/deprecated_dynamic_methods_test.rb
index 77a609f49a..09ca61aa1b 100644
--- a/activerecord/test/cases/deprecated_dynamic_methods_test.rb
+++ b/activerecord/test/cases/deprecated_dynamic_methods_test.rb
@@ -45,32 +45,6 @@ class DeprecatedDynamicMethodsTest < ActiveRecord::TestCase
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
- found_customers = Customer.find_all_by_balance(balance)
- assert_equal 1, found_customers.size
- assert_equal customers(:david), found_customers.first
- end
-
- def test_find_all_by_two_attributes_that_are_both_aggregates
- balance = customers(:david).balance
- address = customers(:david).address
- assert_kind_of Money, balance
- assert_kind_of Address, address
- found_customers = Customer.find_all_by_balance_and_address(balance, address)
- assert_equal 1, found_customers.size
- assert_equal customers(:david), found_customers.first
- end
-
- def test_find_all_by_two_attributes_with_one_being_an_aggregate
- balance = customers(:david).balance
- assert_kind_of Money, balance
- found_customers = Customer.find_all_by_balance_and_name(balance, customers(:david).name)
- assert_equal 1, found_customers.size
- assert_equal customers(:david), found_customers.first
- end
-
def test_find_all_by_one_attribute_with_options
topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC")
assert_equal topics(:first), topics.last
@@ -137,14 +111,6 @@ class DeprecatedDynamicMethodsTest < ActiveRecord::TestCase
assert_equal 17, sig38.firm_id
end
- def test_find_or_create_from_two_attributes_with_one_being_an_aggregate
- number_of_customers = Customer.count
- created_customer = Customer.find_or_create_by_balance_and_name(Money.new(123), "Elizabeth")
- assert_equal number_of_customers + 1, Customer.count
- assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123), "Elizabeth")
- assert created_customer.persisted?
- end
-
def test_find_or_create_from_one_attribute_and_hash
number_of_companies = Company.count
sig38 = Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
@@ -167,38 +133,12 @@ class DeprecatedDynamicMethodsTest < ActiveRecord::TestCase
assert_equal 23, sig38.client_of
end
- def test_find_or_create_from_one_aggregate_attribute
- number_of_customers = Customer.count
- created_customer = Customer.find_or_create_by_balance(Money.new(123))
- assert_equal number_of_customers + 1, Customer.count
- assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123))
- assert created_customer.persisted?
- end
-
- def test_find_or_create_from_one_aggregate_attribute_and_hash
- number_of_customers = Customer.count
- balance = Money.new(123)
- name = "Elizabeth"
- created_customer = Customer.find_or_create_by_balance({:balance => balance, :name => name})
- assert_equal number_of_customers + 1, Customer.count
- assert_equal created_customer, Customer.find_or_create_by_balance({:balance => balance, :name => name})
- assert created_customer.persisted?
- assert_equal balance, created_customer.balance
- assert_equal name, created_customer.name
- end
-
def test_find_or_initialize_from_one_attribute
sig38 = Company.find_or_initialize_by_name("38signals")
assert_equal "38signals", sig38.name
assert !sig38.persisted?
end
- def test_find_or_initialize_from_one_aggregate_attribute
- new_customer = Customer.find_or_initialize_by_balance(Money.new(123))
- assert_equal 123, new_customer.balance.amount
- assert !new_customer.persisted?
- end
-
def test_find_or_initialize_from_one_attribute_should_not_set_attribute_even_when_protected
c = Company.find_or_initialize_by_name({:name => "Fortune 1000", :rating => 1000})
assert_equal "Fortune 1000", c.name
@@ -285,13 +225,6 @@ class DeprecatedDynamicMethodsTest < ActiveRecord::TestCase
assert_raise(ArgumentError) { Topic.find_or_initialize_by_title_and_author_name("Another topic") }
end
- def test_find_or_initialize_from_one_aggregate_attribute_and_one_not
- new_customer = Customer.find_or_initialize_by_balance_and_name(Money.new(123), "Elizabeth")
- assert_equal 123, new_customer.balance.amount
- assert_equal "Elizabeth", new_customer.name
- assert !new_customer.persisted?
- end
-
def test_find_or_initialize_from_one_attribute_and_hash
sig38 = Company.find_or_initialize_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
assert_equal "38signals", sig38.name
@@ -300,15 +233,6 @@ class DeprecatedDynamicMethodsTest < ActiveRecord::TestCase
assert !sig38.persisted?
end
- def test_find_or_initialize_from_one_aggregate_attribute_and_hash
- balance = Money.new(123)
- name = "Elizabeth"
- new_customer = Customer.find_or_initialize_by_balance({:balance => balance, :name => name})
- assert_equal balance, new_customer.balance
- assert_equal name, new_customer.name
- assert !new_customer.persisted?
- end
-
def test_find_last_by_one_attribute
assert_equal Topic.last, Topic.find_last_by_title(Topic.last.title)
assert_nil Topic.find_last_by_title("A title with no matches")
@@ -600,7 +524,8 @@ class DynamicScopeMatchTest < ActiveRecord::TestCase
end
def test_scoped_by
- match = ActiveRecord::DynamicMatchers::Method.match(nil, "scoped_by_age_and_sex_and_location")
+ model = stub(attribute_aliases: {})
+ match = ActiveRecord::DynamicMatchers::Method.match(model, "scoped_by_age_and_sex_and_location")
assert_not_nil match
assert_equal %w(age sex location), match.attribute_names
end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 46d485135f..97ffc068af 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -78,7 +78,7 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal old_created_on, pirate.created_on_was
end
end
-
+
def test_setting_time_attributes_with_time_zone_field_to_itself_should_not_be_marked_as_a_change
in_time_zone 'Paris' do
target = Class.new(ActiveRecord::Base)
@@ -496,16 +496,6 @@ class DirtyTest < ActiveRecord::TestCase
assert_not_nil pirate.previous_changes['updated_on'][1]
assert !pirate.previous_changes.key?('parrot_id')
assert !pirate.previous_changes.key?('created_on')
-
- pirate = Pirate.find_by_catchphrase("Ahoy!")
- pirate.update_attribute(:catchphrase, "Ninjas suck!")
-
- assert_equal 2, pirate.previous_changes.size
- assert_equal ["Ahoy!", "Ninjas suck!"], pirate.previous_changes['catchphrase']
- assert_not_nil pirate.previous_changes['updated_on'][0]
- assert_not_nil pirate.previous_changes['updated_on'][1]
- assert !pirate.previous_changes.key?('parrot_id')
- assert !pirate.previous_changes.key?('created_on')
end
if ActiveRecord::Base.connection.supports_migrations?
diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb
index e118add44c..91e1df91cd 100644
--- a/activerecord/test/cases/explain_subscriber_test.rb
+++ b/activerecord/test/cases/explain_subscriber_test.rb
@@ -6,14 +6,14 @@ if ActiveRecord::Base.connection.supports_explain?
def test_collects_nothing_if_available_queries_for_explain_is_nil
with_queries(nil) do
- SUBSCRIBER.call
+ SUBSCRIBER.finish(nil, nil, {})
assert_nil Thread.current[:available_queries_for_explain]
end
end
def test_collects_nothing_if_the_payload_has_an_exception
with_queries([]) do |queries|
- SUBSCRIBER.call(:exception => Exception.new)
+ SUBSCRIBER.finish(nil, nil, :exception => Exception.new)
assert queries.empty?
end
end
@@ -21,7 +21,7 @@ if ActiveRecord::Base.connection.supports_explain?
def test_collects_nothing_for_ignored_payloads
with_queries([]) do |queries|
ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip|
- SUBSCRIBER.call(:name => ip)
+ SUBSCRIBER.finish(nil, nil, :name => ip)
end
assert queries.empty?
end
@@ -31,7 +31,7 @@ if ActiveRecord::Base.connection.supports_explain?
sql = 'select 1 from users'
binds = [1, 2]
with_queries([]) do |queries|
- SUBSCRIBER.call(:name => 'SQL', :sql => sql, :binds => binds)
+ SUBSCRIBER.finish(nil, nil, :name => 'SQL', :sql => sql, :binds => binds)
assert_equal 1, queries.size
assert_equal sql, queries[0][0]
assert_equal binds, queries[0][1]
@@ -45,4 +45,4 @@ if ActiveRecord::Base.connection.supports_explain?
Thread.current[:available_queries_for_explain] = nil
end
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/cases/finder_respond_to_test.rb b/activerecord/test/cases/finder_respond_to_test.rb
index eedbaac3bd..9440cd429a 100644
--- a/activerecord/test/cases/finder_respond_to_test.rb
+++ b/activerecord/test/cases/finder_respond_to_test.rb
@@ -36,6 +36,11 @@ class FinderRespondToTest < ActiveRecord::TestCase
assert_respond_to Topic, :find_by_title_and_author_name
end
+ def test_should_respond_to_find_all_by_an_aliased_attribute
+ ensure_topic_method_is_not_cached(:find_by_heading)
+ assert_respond_to Topic, :find_by_heading
+ end
+
def test_should_respond_to_find_or_initialize_from_one_attribute
ensure_topic_method_is_not_cached(:find_or_initialize_by_title)
assert_respond_to Topic, :find_or_initialize_by_title
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index aa44307bc2..e62d984ebd 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -79,21 +79,6 @@ class FinderTest < ActiveRecord::TestCase
assert !Topic.exists?
end
- def test_exists_with_aggregate_having_three_mappings
- existing_address = customers(:david).address
- assert Customer.exists?(:address => existing_address)
- end
-
- def test_exists_with_aggregate_having_three_mappings_with_one_difference
- existing_address = customers(:david).address
- assert !Customer.exists?(:address =>
- Address.new(existing_address.street, existing_address.city, existing_address.country + "1"))
- assert !Customer.exists?(:address =>
- Address.new(existing_address.street, existing_address.city + "1", existing_address.country))
- assert !Customer.exists?(:address =>
- Address.new(existing_address.street + "1", existing_address.city, existing_address.country))
- end
-
def test_exists_does_not_instantiate_records
Developer.expects(:instantiate).never
Developer.exists?
@@ -312,14 +297,6 @@ class FinderTest < ActiveRecord::TestCase
assert_equal companies(:rails_core), firms.first
end
- def test_find_on_hash_conditions_with_explicit_table_name_and_aggregate
- david = customers(:david)
- assert Customer.scoped(:where => { 'customers.name' => david.name, :address => david.address }).find(david.id)
- assert_raise(ActiveRecord::RecordNotFound) {
- Customer.scoped(:where => { 'customers.name' => david.name + "1", :address => david.address }).find(david.id)
- }
- end
-
def test_find_on_association_proxy_conditions
assert_equal [1, 2, 3, 5, 6, 7, 8, 9, 10, 12], Comment.where(post_id: authors(:david).posts).map(&:id).sort
end
@@ -394,48 +371,6 @@ class FinderTest < ActiveRecord::TestCase
assert_nil topic.last_read
end
- def test_hash_condition_find_with_aggregate_having_one_mapping
- balance = customers(:david).balance
- assert_kind_of Money, balance
- found_customer = Customer.scoped(:where => {:balance => balance}).first
- assert_equal customers(:david), found_customer
- end
-
- def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_aggregate
- gps_location = customers(:david).gps_location
- assert_kind_of GpsLocation, gps_location
- found_customer = Customer.scoped(:where => {:gps_location => gps_location}).first
- assert_equal customers(:david), found_customer
- end
-
- def test_hash_condition_find_with_aggregate_having_one_mapping_and_key_value_being_attribute_value
- balance = customers(:david).balance
- assert_kind_of Money, balance
- found_customer = Customer.scoped(:where => {:balance => balance.amount}).first
- assert_equal customers(:david), found_customer
- end
-
- def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_attribute_value
- gps_location = customers(:david).gps_location
- assert_kind_of GpsLocation, gps_location
- found_customer = Customer.scoped(:where => {:gps_location => gps_location.gps_location}).first
- assert_equal customers(:david), found_customer
- end
-
- def test_hash_condition_find_with_aggregate_having_three_mappings
- address = customers(:david).address
- assert_kind_of Address, address
- found_customer = Customer.scoped(:where => {:address => address}).first
- assert_equal customers(:david), found_customer
- end
-
- def test_hash_condition_find_with_one_condition_being_aggregate_and_another_not
- address = customers(:david).address
- assert_kind_of Address, address
- found_customer = Customer.scoped(:where => {:address => address, :name => customers(:david).name}).first
- assert_equal customers(:david), found_customer
- end
-
def test_condition_utc_time_interpolation_with_default_timezone_local
with_env_tz 'America/New_York' do
with_active_record_default_timezone :local do
@@ -600,42 +535,13 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordNotFound) { Topic.find_by_title!("The First Topic!") }
end
- def test_find_by_one_attribute_with_conditions
- assert_equal accounts(:rails_core_account), Account.where('firm_id = ?', 6).find_by_credit_limit(50)
+ def test_find_by_one_attribute_that_is_an_alias
+ assert_equal topics(:first), Topic.find_by_heading("The First Topic")
+ assert_nil Topic.find_by_heading("The First Topic!")
end
- def test_find_by_one_attribute_that_is_an_aggregate
- address = customers(:david).address
- assert_kind_of Address, address
- found_customer = Customer.find_by_address(address)
- assert_equal customers(:david), found_customer
- end
-
- def test_find_by_one_attribute_that_is_an_aggregate_with_one_attribute_difference
- address = customers(:david).address
- assert_kind_of Address, address
- missing_address = Address.new(address.street, address.city, address.country + "1")
- assert_nil Customer.find_by_address(missing_address)
- missing_address = Address.new(address.street, address.city + "1", address.country)
- assert_nil Customer.find_by_address(missing_address)
- missing_address = Address.new(address.street + "1", address.city, address.country)
- assert_nil Customer.find_by_address(missing_address)
- end
-
- def test_find_by_two_attributes_that_are_both_aggregates
- balance = customers(:david).balance
- address = customers(:david).address
- assert_kind_of Money, balance
- assert_kind_of Address, address
- found_customer = Customer.find_by_balance_and_address(balance, address)
- assert_equal customers(:david), found_customer
- end
-
- def test_find_by_two_attributes_with_one_being_an_aggregate
- balance = customers(:david).balance
- assert_kind_of Money, balance
- found_customer = Customer.find_by_balance_and_name(balance, customers(:david).name)
- assert_equal customers(:david), found_customer
+ def test_find_by_one_attribute_with_conditions
+ assert_equal accounts(:rails_core_account), Account.where('firm_id = ?', 6).find_by_credit_limit(50)
end
def test_dynamic_finder_on_one_attribute_with_conditions_returns_same_results_after_caching
diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb
index d9e350abc0..a86b165c78 100644
--- a/activerecord/test/cases/json_serialization_test.rb
+++ b/activerecord/test/cases/json_serialization_test.rb
@@ -83,6 +83,53 @@ class JsonSerializationTest < ActiveRecord::TestCase
assert_match %r{"favorite_quote":"Constraints are liberating"}, methods_json
end
+ def test_uses_serializable_hash_with_only_option
+ def @contact.serializable_hash(options=nil)
+ super(only: %w(name))
+ end
+
+ json = @contact.to_json
+ assert_match %r{"name":"Konata Izumi"}, json
+ assert_no_match %r{awesome}, json
+ assert_no_match %r{age}, json
+ end
+
+ def test_uses_serializable_hash_with_except_option
+ def @contact.serializable_hash(options=nil)
+ super(except: %w(age))
+ end
+
+ json = @contact.to_json
+ assert_match %r{"name":"Konata Izumi"}, json
+ assert_match %r{"awesome":true}, json
+ assert_no_match %r{age}, json
+ end
+
+ def test_does_not_include_inheritance_column_from_sti
+ @contact = ContactSti.new(@contact.attributes)
+ assert_equal 'ContactSti', @contact.type
+
+ json = @contact.to_json
+ assert_match %r{"name":"Konata Izumi"}, json
+ assert_no_match %r{type}, json
+ assert_no_match %r{ContactSti}, json
+ end
+
+ def test_serializable_hash_with_default_except_option_and_excluding_inheritance_column_from_sti
+ @contact = ContactSti.new(@contact.attributes)
+ assert_equal 'ContactSti', @contact.type
+
+ def @contact.serializable_hash(options={})
+ super({ except: %w(age) }.merge!(options))
+ end
+
+ json = @contact.to_json
+ assert_match %r{"name":"Konata Izumi"}, json
+ assert_no_match %r{age}, json
+ assert_no_match %r{type}, json
+ assert_no_match %r{ContactSti}, json
+ end
+
def test_serializable_hash_should_not_modify_options_in_argument
options = { :only => :name }
@contact.serializable_hash(options)
diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb
index 214546802a..73a01906b9 100644
--- a/activerecord/test/cases/mass_assignment_security_test.rb
+++ b/activerecord/test/cases/mass_assignment_security_test.rb
@@ -251,6 +251,65 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
assert !Task.new.respond_to?("#{method}=")
end
end
+
+ test "ActiveRecord::Model.whitelist_attributes works for models which include Model" do
+ begin
+ prev, ActiveRecord::Model.whitelist_attributes = ActiveRecord::Model.whitelist_attributes, true
+
+ klass = Class.new { include ActiveRecord::Model }
+ assert_equal ActiveModel::MassAssignmentSecurity::WhiteList, klass.active_authorizers[:default].class
+ assert_equal [], klass.active_authorizers[:default].to_a
+ ensure
+ ActiveRecord::Model.whitelist_attributes = prev
+ end
+ end
+
+ test "ActiveRecord::Model.whitelist_attributes works for models which inherit Base" do
+ begin
+ prev, ActiveRecord::Model.whitelist_attributes = ActiveRecord::Model.whitelist_attributes, true
+
+ klass = Class.new(ActiveRecord::Base)
+ assert_equal ActiveModel::MassAssignmentSecurity::WhiteList, klass.active_authorizers[:default].class
+ assert_equal [], klass.active_authorizers[:default].to_a
+
+ klass.attr_accessible 'foo'
+ assert_equal ['foo'], Class.new(klass).active_authorizers[:default].to_a
+ ensure
+ ActiveRecord::Model.whitelist_attributes = prev
+ end
+ end
+
+ test "ActiveRecord::Model.mass_assignment_sanitizer works for models which include Model" do
+ begin
+ sanitizer = Object.new
+ prev, ActiveRecord::Model.mass_assignment_sanitizer = ActiveRecord::Model.mass_assignment_sanitizer, sanitizer
+
+ klass = Class.new { include ActiveRecord::Model }
+ assert_equal sanitizer, klass._mass_assignment_sanitizer
+
+ ActiveRecord::Model.mass_assignment_sanitizer = nil
+ klass = Class.new { include ActiveRecord::Model }
+ assert_not_nil klass._mass_assignment_sanitizer
+ ensure
+ ActiveRecord::Model.mass_assignment_sanitizer = prev
+ end
+ end
+
+ test "ActiveRecord::Model.mass_assignment_sanitizer works for models which inherit Base" do
+ begin
+ sanitizer = Object.new
+ prev, ActiveRecord::Model.mass_assignment_sanitizer = ActiveRecord::Model.mass_assignment_sanitizer, sanitizer
+
+ klass = Class.new(ActiveRecord::Base)
+ assert_equal sanitizer, klass._mass_assignment_sanitizer
+
+ sanitizer2 = Object.new
+ klass.mass_assignment_sanitizer = sanitizer2
+ assert_equal sanitizer2, Class.new(klass)._mass_assignment_sanitizer
+ ensure
+ ActiveRecord::Model.mass_assignment_sanitizer = prev
+ end
+ end
end
diff --git a/activerecord/test/cases/mysql_rake_test.rb b/activerecord/test/cases/mysql_rake_test.rb
new file mode 100644
index 0000000000..42a11b0171
--- /dev/null
+++ b/activerecord/test/cases/mysql_rake_test.rb
@@ -0,0 +1,246 @@
+require 'cases/helper'
+require 'mysql'
+
+module ActiveRecord
+ class MysqlDBCreateTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:create_database => true)
+ @configuration = {
+ 'adapter' => 'mysql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_establishes_connection_without_database
+ ActiveRecord::Base.expects(:establish_connection).
+ with('adapter' => 'mysql', 'database' => nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_creates_database_with_default_options
+ @connection.expects(:create_database).
+ with('my-app-db', {:charset => 'utf8', :collation => 'utf8_unicode_ci'})
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_creates_database_with_given_options
+ @connection.expects(:create_database).
+ with('my-app-db', {:charset => 'latin', :collation => 'latin_ci'})
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge(
+ 'charset' => 'latin', 'collation' => 'latin_ci'
+ )
+ end
+
+ def test_establishes_connection_to_database
+ ActiveRecord::Base.expects(:establish_connection).with(@configuration)
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ end
+
+ class MysqlDBCreateAsRootTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:create_database => true, :execute => true)
+ @error = Mysql::Error.new "Invalid permissions"
+ @configuration = {
+ 'adapter' => 'mysql',
+ 'database' => 'my-app-db',
+ 'username' => 'pat',
+ 'password' => 'wossname'
+ }
+
+ $stdin.stubs(:gets).returns("secret\n")
+ $stdout.stubs(:print).returns(nil)
+ @error.stubs(:errno).returns(1045)
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).raises(@error).then.
+ returns(true)
+ end
+
+ def test_root_password_is_requested
+ $stdin.expects(:gets).returns("secret\n")
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_connection_established_as_root
+ ActiveRecord::Base.expects(:establish_connection).with({
+ 'adapter' => 'mysql',
+ 'database' => nil,
+ 'username' => 'root',
+ 'password' => 'secret'
+ })
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_database_created_by_root
+ @connection.expects(:create_database).
+ with('my-app-db', :charset => 'utf8', :collation => 'utf8_unicode_ci')
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_grant_privileges_for_normal_user
+ @connection.expects(:execute).with("GRANT ALL PRIVILEGES ON my-app-db.* TO 'pat'@'localhost' IDENTIFIED BY 'wossname' WITH GRANT OPTION;")
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_connection_established_as_normal_user
+ ActiveRecord::Base.expects(:establish_connection).returns do
+ ActiveRecord::Base.expects(:establish_connection).with({
+ 'adapter' => 'mysql',
+ 'database' => 'my-app-db',
+ 'username' => 'pat',
+ 'password' => 'secret'
+ })
+
+ raise @error
+ end
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_sends_output_to_stderr_when_other_errors
+ @error.stubs(:errno).returns(42)
+
+ $stderr.expects(:puts).at_least_once.returns(nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ end
+
+ class MySQLDBDropTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:drop_database => true)
+ @configuration = {
+ 'adapter' => 'mysql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_establishes_connection_to_mysql_database
+ ActiveRecord::Base.expects(:establish_connection).with @configuration
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+
+ def test_drops_database
+ @connection.expects(:drop_database).with('my-app-db')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+ end
+
+ class MySQLPurgeTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:recreate_database => true)
+ @configuration = {
+ 'adapter' => 'mysql',
+ 'database' => 'test-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_establishes_connection_to_test_database
+ ActiveRecord::Base.expects(:establish_connection).with(:test)
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+
+ def test_recreates_database_with_the_default_options
+ @connection.expects(:recreate_database).
+ with('test-db', {:charset => 'utf8', :collation => 'utf8_unicode_ci'})
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+
+ def test_recreates_database_with_the_given_options
+ @connection.expects(:recreate_database).
+ with('test-db', {:charset => 'latin', :collation => 'latin_ci'})
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration.merge(
+ 'charset' => 'latin', 'collation' => 'latin_ci'
+ )
+ end
+ end
+
+ class MysqlDBCharsetTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:create_database => true)
+ @configuration = {
+ 'adapter' => 'mysql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_db_retrieves_charset
+ @connection.expects(:charset)
+ ActiveRecord::Tasks::DatabaseTasks.charset @configuration
+ end
+ end
+
+ class MySQLStructureDumpTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:structure_dump => true)
+ @configuration = {
+ 'adapter' => 'mysql',
+ 'database' => 'test-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_structure_dump
+ filename = "awesome-file.sql"
+ ActiveRecord::Base.expects(:establish_connection).with(@configuration)
+ @connection.expects(:structure_dump)
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ assert File.exists?(filename)
+ ensure
+ FileUtils.rm(filename)
+ end
+ end
+
+ class MySQLStructureLoadTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub
+ @configuration = {
+ 'adapter' => 'mysql',
+ 'database' => 'test-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_structure_load
+ filename = "awesome-file.sql"
+ ActiveRecord::Base.expects(:establish_connection).with(@configuration)
+ @connection.expects(:execute).twice
+
+ open(filename, 'w') { |f| f.puts("SELECT CURDATE();") }
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
+ ensure
+ FileUtils.rm(filename)
+ end
+ end
+
+end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 0559bbbe9a..3daa033ed0 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -781,6 +781,16 @@ module NestedAttributesOnACollectionAssociationTests
assert_nothing_raised(NoMethodError) { @pirate.save! }
end
+ def test_numeric_colum_changes_from_zero_to_no_empty_string
+ Man.accepts_nested_attributes_for(:interests)
+ Interest.validates_numericality_of(:zine_id)
+ man = Man.create(:name => 'John')
+ interest = man.interests.create(:topic=>'bar',:zine_id => 0)
+ assert interest.save
+
+ assert !man.update_attributes({:interests_attributes => { :id => interest.id, :zine_id => 'foo' }})
+ end
+
private
def association_setter
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index fecdf2b705..b7b77b24af 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -371,71 +371,10 @@ class PersistencesTest < ActiveRecord::TestCase
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_does_not_choke_on_nil
assert Topic.find(1).update_attributes(nil)
end
- def test_update_attribute_for_readonly_attribute
- minivan = Minivan.find('m1')
- assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_attribute(:color, 'black') }
- end
-
- # This test is correct, but it is hard to fix it since
- # update_attribute trigger simply call save! that triggers
- # all callbacks.
- # 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
- 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_updated_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_column
topic = Topic.find(1)
topic.update_column("approved", true)
@@ -467,7 +406,7 @@ class PersistencesTest < ActiveRecord::TestCase
def test_update_column_should_not_leave_the_object_dirty
topic = Topic.find(1)
- topic.update_attribute("content", "Have a nice day")
+ topic.update_column("content", "Have a nice day")
topic.reload
topic.update_column(:content, "You too")
diff --git a/activerecord/test/cases/postgresql_rake_test.rb b/activerecord/test/cases/postgresql_rake_test.rb
new file mode 100644
index 0000000000..e8769bd4df
--- /dev/null
+++ b/activerecord/test/cases/postgresql_rake_test.rb
@@ -0,0 +1,200 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class PostgreSQLDBCreateTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:create_database => true)
+ @configuration = {
+ 'adapter' => 'postgresql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_establishes_connection_to_postgresql_database
+ ActiveRecord::Base.expects(:establish_connection).with(
+ 'adapter' => 'postgresql',
+ 'database' => 'postgres',
+ 'schema_search_path' => 'public'
+ )
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_creates_database_with_default_encoding
+ @connection.expects(:create_database).
+ with('my-app-db', @configuration.merge('encoding' => 'utf8'))
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_creates_database_with_given_encoding
+ @connection.expects(:create_database).
+ with('my-app-db', @configuration.merge('encoding' => 'latin'))
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration.
+ merge('encoding' => 'latin')
+ end
+
+ def test_establishes_connection_to_new_database
+ ActiveRecord::Base.expects(:establish_connection).with(@configuration)
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_db_create_with_error_prints_message
+ ActiveRecord::Base.stubs(:establish_connection).raises(Exception)
+
+ $stderr.stubs(:puts).returns(true)
+ $stderr.expects(:puts).
+ with("Couldn't create database for #{@configuration.inspect}")
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ end
+
+ class PostgreSQLDBDropTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:drop_database => true)
+ @configuration = {
+ 'adapter' => 'postgresql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_establishes_connection_to_postgresql_database
+ ActiveRecord::Base.expects(:establish_connection).with(
+ 'adapter' => 'postgresql',
+ 'database' => 'postgres',
+ 'schema_search_path' => 'public'
+ )
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+
+ def test_drops_database
+ @connection.expects(:drop_database).with('my-app-db')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+ end
+
+ class PostgreSQLPurgeTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:create_database => true, :drop_database => true)
+ @configuration = {
+ 'adapter' => 'postgresql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:clear_active_connections!).returns(true)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_clears_active_connections
+ ActiveRecord::Base.expects(:clear_active_connections!)
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+
+ def test_establishes_connection_to_postgresql_database
+ ActiveRecord::Base.expects(:establish_connection).with(
+ 'adapter' => 'postgresql',
+ 'database' => 'postgres',
+ 'schema_search_path' => 'public'
+ )
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+
+ def test_drops_database
+ @connection.expects(:drop_database).with('my-app-db')
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+
+ def test_creates_database
+ @connection.expects(:create_database).
+ with('my-app-db', @configuration.merge('encoding' => 'utf8'))
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+
+ def test_establishes_connection
+ ActiveRecord::Base.expects(:establish_connection).with(@configuration)
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+ end
+
+ class PostgreSQLDBCharsetTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:create_database => true)
+ @configuration = {
+ 'adapter' => 'postgresql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_db_retrieves_charset
+ @connection.expects(:encoding)
+ ActiveRecord::Tasks::DatabaseTasks.charset @configuration
+ end
+ end
+
+ class PostgreSQLStructureDumpTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:structure_dump => true)
+ @configuration = {
+ 'adapter' => 'postgresql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ Kernel.stubs(:system)
+ end
+
+ def test_structure_dump
+ filename = "awesome-file.sql"
+ Kernel.expects(:system).with("pg_dump -i -s -x -O -f #{filename} my-app-db").returns(true)
+ @connection.expects(:schema_search_path).returns("foo")
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ assert File.exists?(filename)
+ ensure
+ FileUtils.rm(filename)
+ end
+ end
+
+ class PostgreSQLStructureLoadTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub
+ @configuration = {
+ 'adapter' => 'postgresql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ Kernel.stubs(:system)
+ end
+
+ def test_structure_dump
+ filename = "awesome-file.sql"
+ Kernel.expects(:system).with("psql -f #{filename} my-app-db")
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
+ end
+ end
+
+end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index 7dd5698dcf..bac3376c9f 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -83,30 +83,6 @@ class ReflectionTest < ActiveRecord::TestCase
end
end
- def test_aggregation_reflection
- reflection_for_address = AggregateReflection.new(
- :composed_of, :address, { :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ] }, Customer
- )
-
- reflection_for_balance = AggregateReflection.new(
- :composed_of, :balance, { :class_name => "Money", :mapping => %w(balance amount) }, Customer
- )
-
- reflection_for_gps_location = AggregateReflection.new(
- :composed_of, :gps_location, { }, Customer
- )
-
- assert Customer.reflect_on_all_aggregations.include?(reflection_for_gps_location)
- assert Customer.reflect_on_all_aggregations.include?(reflection_for_balance)
- assert Customer.reflect_on_all_aggregations.include?(reflection_for_address)
-
- assert_equal reflection_for_address, Customer.reflect_on_aggregation(:address)
-
- assert_equal Address, Customer.reflect_on_aggregation(:address).klass
-
- assert_equal Money, Customer.reflect_on_aggregation(:balance).klass
- end
-
def test_reflect_on_all_autosave_associations
expected = Pirate.reflect_on_all_associations.select { |r| r.options[:autosave] }
received = Pirate.reflect_on_all_autosave_associations
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 6c5bee7382..8544d36aa8 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1284,4 +1284,31 @@ class RelationTest < ActiveRecord::TestCase
Post.scoped.find_by!("1 = 0")
end
end
+
+ test "loaded relations cannot be mutated by multi value methods" do
+ relation = Post.scoped
+ relation.to_a
+
+ assert_raises(ActiveRecord::ImmutableRelation) do
+ relation.where! 'foo'
+ end
+ end
+
+ test "loaded relations cannot be mutated by single value methods" do
+ relation = Post.scoped
+ relation.to_a
+
+ assert_raises(ActiveRecord::ImmutableRelation) do
+ relation.limit! 5
+ end
+ end
+
+ test "loaded relations cannot be mutated by merge!" do
+ relation = Post.scoped
+ relation.to_a
+
+ assert_raises(ActiveRecord::ImmutableRelation) do
+ relation.merge! where: 'foo'
+ end
+ end
end
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index ab80dd1d6d..01dd25a9df 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -257,6 +257,13 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
end
+ def test_schema_dump_includes_uuid_shorthand_definition
+ output = standard_dump
+ if %r{create_table "poistgresql_uuids"} =~ output
+ assert_match %r{t.uuid "guid"}, output
+ end
+ end
+
def test_schema_dump_includes_hstores_shorthand_definition
output = standard_dump
if %r{create_table "postgresql_hstores"} =~ output
@@ -294,4 +301,36 @@ class SchemaDumperTest < ActiveRecord::TestCase
output = standard_dump
assert_match %r{create_table "subscribers", :id => false}, output
end
+
+ class CreateDogMigration < ActiveRecord::Migration
+ def up
+ create_table("dogs") do |t|
+ t.column :name, :string
+ end
+ add_index "dogs", [:name]
+ end
+ def down
+ drop_table("dogs")
+ end
+ end
+
+ def test_schema_dump_with_table_name_prefix_and_suffix
+ original, $stdout = $stdout, StringIO.new
+ ActiveRecord::Base.table_name_prefix = 'foo_'
+ ActiveRecord::Base.table_name_suffix = '_bar'
+
+ migration = CreateDogMigration.new
+ migration.migrate(:up)
+
+ output = standard_dump
+ assert_no_match %r{create_table "foo_.+_bar"}, output
+ assert_no_match %r{create_index "foo_.+_bar"}, output
+ assert_no_match %r{create_table "schema_migrations"}, output
+ ensure
+ migration.migrate(:down)
+
+ ActiveRecord::Base.table_name_suffix = ActiveRecord::Base.table_name_prefix = ''
+ $stdout = original
+ end
+
end
diff --git a/activerecord/test/cases/sqlite_rake_test.rb b/activerecord/test/cases/sqlite_rake_test.rb
new file mode 100644
index 0000000000..b5557fc953
--- /dev/null
+++ b/activerecord/test/cases/sqlite_rake_test.rb
@@ -0,0 +1,170 @@
+require 'cases/helper'
+require 'pathname'
+
+module ActiveRecord
+ class SqliteDBCreateTest < ActiveRecord::TestCase
+ def setup
+ @database = 'db_create.sqlite3'
+ @connection = stub :connection
+ @configuration = {
+ 'adapter' => 'sqlite3',
+ 'database' => @database
+ }
+
+ File.stubs(:exist?).returns(false)
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_db_checks_database_exists
+ File.expects(:exist?).with(@database).returns(false)
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
+ end
+
+ def test_db_create_when_file_exists
+ File.stubs(:exist?).returns(true)
+
+ $stderr.expects(:puts).with("#{@database} already exists")
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
+ end
+
+ def test_db_create_with_file_does_nothing
+ File.stubs(:exist?).returns(true)
+ $stderr.stubs(:puts).returns(nil)
+
+ ActiveRecord::Base.expects(:establish_connection).never
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
+ end
+
+ def test_db_create_establishes_a_connection
+ ActiveRecord::Base.expects(:establish_connection).with(@configuration)
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
+ end
+
+ def test_db_create_with_error_prints_message
+ ActiveRecord::Base.stubs(:establish_connection).raises(Exception)
+
+ $stderr.stubs(:puts).returns(true)
+ $stderr.expects(:puts).
+ with("Couldn't create database for #{@configuration.inspect}")
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
+ end
+ end
+
+ class SqliteDBDropTest < ActiveRecord::TestCase
+ def setup
+ @database = "db_create.sqlite3"
+ @path = stub(:to_s => '/absolute/path', :absolute? => true)
+ @configuration = {
+ 'adapter' => 'sqlite3',
+ 'database' => @database
+ }
+
+ Pathname.stubs(:new).returns(@path)
+ File.stubs(:join).returns('/former/relative/path')
+ FileUtils.stubs(:rm).returns(true)
+ end
+
+ def test_creates_path_from_database
+ Pathname.expects(:new).with(@database).returns(@path)
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root'
+ end
+
+ def test_removes_file_with_absolute_path
+ File.stubs(:exist?).returns(true)
+ @path.stubs(:absolute?).returns(true)
+
+ FileUtils.expects(:rm).with('/absolute/path')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root'
+ end
+
+ def test_generates_absolute_path_with_given_root
+ @path.stubs(:absolute?).returns(false)
+
+ File.expects(:join).with('/rails/root', @path).
+ returns('/former/relative/path')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root'
+ end
+
+ def test_removes_file_with_relative_path
+ File.stubs(:exist?).returns(true)
+ @path.stubs(:absolute?).returns(false)
+
+ FileUtils.expects(:rm).with('/former/relative/path')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root'
+ end
+ end
+
+ class SqliteDBCharsetTest < ActiveRecord::TestCase
+ def setup
+ @database = 'db_create.sqlite3'
+ @connection = stub :connection
+ @configuration = {
+ 'adapter' => 'sqlite3',
+ 'database' => @database
+ }
+
+ File.stubs(:exist?).returns(false)
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_db_retrieves_charset
+ @connection.expects(:encoding)
+ ActiveRecord::Tasks::DatabaseTasks.charset @configuration, '/rails/root'
+ end
+ end
+
+ class SqliteStructureDumpTest < ActiveRecord::TestCase
+ def setup
+ @database = "db_create.sqlite3"
+ @configuration = {
+ 'adapter' => 'sqlite3',
+ 'database' => @database
+ }
+ end
+
+ def test_structure_dump
+ dbfile = @database
+ filename = "awesome-file.sql"
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump @configuration, filename, '/rails/root'
+ assert File.exists?(dbfile)
+ assert File.exists?(filename)
+ ensure
+ FileUtils.rm(filename)
+ FileUtils.rm(dbfile)
+ end
+ end
+
+ class SqliteStructureLoadTest < ActiveRecord::TestCase
+ def setup
+ @database = "db_create.sqlite3"
+ @configuration = {
+ 'adapter' => 'sqlite3',
+ 'database' => @database
+ }
+ end
+
+ def test_structure_load
+ dbfile = @database
+ filename = "awesome-file.sql"
+
+ open(filename, 'w') { |f| f.puts("select datetime('now', 'localtime');") }
+ ActiveRecord::Tasks::DatabaseTasks.structure_load @configuration, filename, '/rails/root'
+ assert File.exists?(dbfile)
+ ensure
+ FileUtils.rm(filename)
+ FileUtils.rm(dbfile)
+ end
+ end
+end
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
index 79476ed2a4..e401dd4b12 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -13,7 +13,7 @@ class StoreTest < ActiveRecord::TestCase
assert_equal 'black', @john.color
assert_nil @john.homepage
end
-
+
test "writing store attributes through accessors" do
@john.color = 'red'
@john.homepage = '37signals.com'
@@ -111,4 +111,8 @@ class StoreTest < ActiveRecord::TestCase
@john.is_a_good_guy = false
assert_equal false, @john.is_a_good_guy
end
+
+ test "stored attributes are returned" do
+ assert_equal [:color, :homepage], Admin::User.stored_attributes[:settings]
+ end
end
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 447aa29ffe..7df6993b30 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -11,7 +11,7 @@ class TimestampTest < ActiveRecord::TestCase
def setup
@developer = Developer.first
- @developer.update_attribute(:updated_at, Time.now.prev_month)
+ @developer.update_column(:updated_at, Time.now.prev_month)
@previously_updated_at = @developer.updated_at
end
@@ -133,7 +133,7 @@ class TimestampTest < ActiveRecord::TestCase
pet = Pet.first
owner = pet.owner
- owner.update_attribute(:happy_at, 3.days.ago)
+ owner.update_column(:happy_at, 3.days.ago)
previously_owner_updated_at = owner.updated_at
pet.name = "I'm a parrot"
diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb
index 12373333b0..7249ae9e4d 100644
--- a/activerecord/test/cases/xml_serialization_test.rb
+++ b/activerecord/test/cases/xml_serialization_test.rb
@@ -74,33 +74,88 @@ end
class DefaultXmlSerializationTest < ActiveRecord::TestCase
def setup
- @xml = Contact.new(:name => 'aaron stack', :age => 25, :avatar => 'binarydata', :created_at => Time.utc(2006, 8, 1), :awesome => false, :preferences => { :gem => 'ruby' }).to_xml
+ @contact = Contact.new(
+ :name => 'aaron stack',
+ :age => 25,
+ :avatar => 'binarydata',
+ :created_at => Time.utc(2006, 8, 1),
+ :awesome => false,
+ :preferences => { :gem => 'ruby' }
+ )
end
def test_should_serialize_string
- assert_match %r{<name>aaron stack</name>}, @xml
+ assert_match %r{<name>aaron stack</name>}, @contact.to_xml
end
def test_should_serialize_integer
- assert_match %r{<age type="integer">25</age>}, @xml
+ assert_match %r{<age type="integer">25</age>}, @contact.to_xml
end
def test_should_serialize_binary
- assert_match %r{YmluYXJ5ZGF0YQ==\n</avatar>}, @xml
- assert_match %r{<avatar(.*)(type="binary")}, @xml
- assert_match %r{<avatar(.*)(encoding="base64")}, @xml
+ xml = @contact.to_xml
+ assert_match %r{YmluYXJ5ZGF0YQ==\n</avatar>}, xml
+ assert_match %r{<avatar(.*)(type="binary")}, xml
+ assert_match %r{<avatar(.*)(encoding="base64")}, xml
end
def test_should_serialize_datetime
- assert_match %r{<created-at type=\"dateTime\">2006-08-01T00:00:00Z</created-at>}, @xml
+ assert_match %r{<created-at type=\"dateTime\">2006-08-01T00:00:00Z</created-at>}, @contact.to_xml
end
def test_should_serialize_boolean
- assert_match %r{<awesome type=\"boolean\">false</awesome>}, @xml
+ assert_match %r{<awesome type=\"boolean\">false</awesome>}, @contact.to_xml
end
def test_should_serialize_hash
- assert_match %r{<preferences>\s*<gem>ruby</gem>\s*</preferences>}m, @xml
+ assert_match %r{<preferences>\s*<gem>ruby</gem>\s*</preferences>}m, @contact.to_xml
+ end
+
+ def test_uses_serializable_hash_with_only_option
+ def @contact.serializable_hash(options=nil)
+ super(only: %w(name))
+ end
+
+ xml = @contact.to_xml
+ assert_match %r{<name>aaron stack</name>}, xml
+ assert_no_match %r{age}, xml
+ assert_no_match %r{awesome}, xml
+ end
+
+ def test_uses_serializable_hash_with_except_option
+ def @contact.serializable_hash(options=nil)
+ super(except: %w(age))
+ end
+
+ xml = @contact.to_xml
+ assert_match %r{<name>aaron stack</name>}, xml
+ assert_match %r{<awesome type=\"boolean\">false</awesome>}, xml
+ assert_no_match %r{age}, xml
+ end
+
+ def test_does_not_include_inheritance_column_from_sti
+ @contact = ContactSti.new(@contact.attributes)
+ assert_equal 'ContactSti', @contact.type
+
+ xml = @contact.to_xml
+ assert_match %r{<name>aaron stack</name>}, xml
+ assert_no_match %r{<type}, xml
+ assert_no_match %r{ContactSti}, xml
+ end
+
+ def test_serializable_hash_with_default_except_option_and_excluding_inheritance_column_from_sti
+ @contact = ContactSti.new(@contact.attributes)
+ assert_equal 'ContactSti', @contact.type
+
+ def @contact.serializable_hash(options={})
+ super({ except: %w(age) }.merge!(options))
+ end
+
+ xml = @contact.to_xml
+ assert_match %r{<name>aaron stack</name>}, xml
+ assert_no_match %r{age}, xml
+ assert_no_match %r{<type}, xml
+ assert_no_match %r{ContactSti}, xml
end
end
diff --git a/activerecord/test/fixtures/reserved_words/distincts_selects.yml b/activerecord/test/fixtures/reserved_words/distinct_select.yml
index 90e8c95fef..d96779ade4 100644
--- a/activerecord/test/fixtures/reserved_words/distincts_selects.yml
+++ b/activerecord/test/fixtures/reserved_words/distinct_select.yml
@@ -1,11 +1,11 @@
-distincts_selects1:
+distinct_select1:
distinct_id: 1
select_id: 1
-distincts_selects2:
+distinct_select2:
distinct_id: 1
select_id: 2
-distincts_selects3:
+distinct_select3:
distinct_id: 2
select_id: 3
diff --git a/activerecord/test/models/contact.rb b/activerecord/test/models/contact.rb
index 3d15c7fbed..a1cb8d62b6 100644
--- a/activerecord/test/models/contact.rb
+++ b/activerecord/test/models/contact.rb
@@ -1,25 +1,40 @@
-class Contact < ActiveRecord::Base
- establish_connection(:adapter => 'fake')
+module ContactFakeColumns
+ def self.extended(base)
+ base.class_eval do
+ establish_connection(:adapter => 'fake')
+
+ connection.tables = [table_name]
+ connection.primary_keys = {
+ table_name => 'id'
+ }
+
+ column :name, :string
+ column :age, :integer
+ column :avatar, :binary
+ column :created_at, :datetime
+ column :awesome, :boolean
+ column :preferences, :string
+ column :alternative_id, :integer
+
+ serialize :preferences
- connection.tables = ['contacts']
- connection.primary_keys = {
- 'contacts' => 'id'
- }
+ belongs_to :alternative, :class_name => 'Contact'
+ end
+ end
# mock out self.columns so no pesky db is needed for these tests
- def self.column(name, sql_type = nil, options = {})
- connection.merge_column('contacts', name, sql_type, options)
+ def column(name, sql_type = nil, options = {})
+ connection.merge_column(table_name, name, sql_type, options)
end
+end
- column :name, :string
- column :age, :integer
- column :avatar, :binary
- column :created_at, :datetime
- column :awesome, :boolean
- column :preferences, :string
- column :alternative_id, :integer
+class Contact < ActiveRecord::Base
+ extend ContactFakeColumns
+end
- serialize :preferences
+class ContactSti < ActiveRecord::Base
+ extend ContactFakeColumns
+ column :type, :string
- belongs_to :alternative, :class_name => 'Contact'
+ def type; 'ContactSti' end
end
diff --git a/activerecord/test/models/customer.rb b/activerecord/test/models/customer.rb
index 7e8e82542f..594c484f20 100644
--- a/activerecord/test/models/customer.rb
+++ b/activerecord/test/models/customer.rb
@@ -1,12 +1,5 @@
class Customer < ActiveRecord::Base
cattr_accessor :gps_conversion_was_run
-
- composed_of :address, :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ], :allow_nil => true
- composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new { |balance| balance.to_money }
- composed_of :gps_location, :allow_nil => true
- composed_of :non_blank_gps_location, :class_name => "GpsLocation", :allow_nil => true, :mapping => %w(gps_location gps_location),
- :converter => lambda { |gps| self.gps_conversion_was_run = true; gps.blank? ? nil : GpsLocation.new(gps)}
- composed_of :fullname, :mapping => %w(name to_s), :constructor => Proc.new { |name| Fullname.parse(name) }, :converter => :parse
end
class Address
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index 83482f4d07..9fefa9ad3a 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -65,11 +65,6 @@ class AuditLog < ActiveRecord::Base
end
DeveloperSalary = Struct.new(:amount)
-class DeveloperWithAggregate < ActiveRecord::Base
- self.table_name = 'developers'
- composed_of :salary, :class_name => 'DeveloperSalary', :mapping => [%w(salary amount)]
-end
-
class DeveloperWithBeforeDestroyRaise < ActiveRecord::Base
self.table_name = 'developers'
has_and_belongs_to_many :projects, :join_table => 'developers_projects', :foreign_key => 'developer_id'
diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb
index 11a0f4ff63..1f719b0858 100644
--- a/activerecord/test/models/member.rb
+++ b/activerecord/test/models/member.rb
@@ -30,3 +30,8 @@ class Member < ActiveRecord::Base
has_many :current_memberships, :conditions => { :favourite => true }
has_many :clubs, :through => :current_memberships
end
+
+class SelfMember < ActiveRecord::Base
+ self.table_name = "members"
+ has_and_belongs_to_many :friends, :class_name => "SelfMember", :join_table => "member_friends"
+end
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index 079e403444..fb27c9d4f0 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -58,6 +58,8 @@ class Topic < ActiveRecord::Base
id
end
+ alias_attribute :heading, :title
+
before_validation :before_validation_for_transaction
before_save :before_save_for_transaction
before_destroy :before_destroy_for_transaction
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index e51db50ae3..5f01f1fc50 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -1,6 +1,6 @@
ActiveRecord::Schema.define do
- %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings
+ %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings postgresql_uuids
postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent).each do |table_name|
execute "DROP TABLE IF EXISTS #{quote_table_name table_name}"
end
@@ -59,6 +59,14 @@ _SQL
_SQL
execute <<_SQL
+ CREATE TABLE postgresql_uuids (
+ id SERIAL PRIMARY KEY,
+ guid uuid,
+ compact_guid uuid
+ );
+_SQL
+
+ execute <<_SQL
CREATE TABLE postgresql_tsvectors (
id SERIAL PRIMARY KEY,
text_vector tsvector
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 5bcb9652cd..ba01ef35f0 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -361,6 +361,11 @@ ActiveRecord::Schema.define do
t.string :extra_data
end
+ create_table :member_friends, :force => true, :id => false do |t|
+ t.integer :member_id
+ t.integer :friend_id
+ end
+
create_table :memberships, :force => true do |t|
t.datetime :joined_on
t.integer :club_id, :member_id
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 804336da91..00fcfe2001 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,5 +1,7 @@
## Rails 4.0.0 (unreleased) ##
+* Add `Time#prev_quarter' and 'Time#next_quarter' short-hands for months_ago(3) and months_since(3). *SungHee Kang*
+
* Remove obsolete and unused `require_association` method from dependencies. *fxn*
* Add `:instance_accessor` option for `config_accessor`.
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 30221f2401..fa38d5c1e3 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -21,5 +21,5 @@ Gem::Specification.new do |s|
s.add_dependency('i18n', '~> 0.6')
s.add_dependency('multi_json', '~> 1.3')
- s.add_dependency('tzinfo', '~> 0.3.31')
+ s.add_dependency('tzinfo', '~> 0.3.33')
end
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index 8a7eb6bc6b..7fe4161fb4 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -202,6 +202,17 @@ class Date
acts_like?(:time) ? result.change(:hour => 0) : result
end
+ # Short-hand for months_ago(3)
+ def prev_quarter
+ months_ago(3)
+ end
+ alias_method :last_quarter, :prev_quarter
+
+ # Short-hand for months_since(3)
+ def next_quarter
+ months_since(3)
+ end
+
# Returns a new Date/DateTime representing the start of the month (1st of the month; DateTime objects will have time set to 0:00)
def beginning_of_month
acts_like?(:time) ? change(:day => 1, :hour => 0) : change(:day => 1)
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 070bfd7af6..efa2d43f20 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -107,7 +107,7 @@ class String
# Replaces underscores with dashes in the string.
#
- # 'puni_puni' # => "puni-puni"
+ # 'puni_puni'.dasherize # => "puni-puni"
def dasherize
ActiveSupport::Inflector.dasherize(self)
end
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 92b8417150..28c8b53b78 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -186,6 +186,17 @@ class Time
months_since(1)
end
+ # Short-hand for months_ago(3)
+ def prev_quarter
+ months_ago(3)
+ end
+ alias_method :last_quarter, :prev_quarter
+
+ # Short-hand for months_since(3)
+ def next_quarter
+ months_since(3)
+ end
+
# Returns number of days to start of this week, week starts on start_day (default is :monday).
def days_to_week_start(start_day = :monday)
start_day_number = DAYS_INTO_WEEK[start_day]
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index 176edefa42..e3b4a7240e 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -10,10 +10,10 @@ module ActiveSupport
# The version the deprecated behavior will be removed, by default.
attr_accessor :deprecation_horizon
end
- self.deprecation_horizon = '3.2'
+ self.deprecation_horizon = '4.1'
# By default, warnings are not silenced and debugging is off.
self.silenced = false
self.debug = false
end
-end
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 6e1c0da991..3e6c8893e9 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -164,7 +164,8 @@ module ActiveSupport
if value.is_a? Hash
value.nested_under_indifferent_access
elsif value.is_a?(Array)
- value.dup.replace(value.map { |e| convert_value(e) })
+ value = value.dup if value.frozen?
+ value.map! { |e| convert_value(e) }
else
value
end
diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb
index bea2ca17f1..e5b4ca2738 100644
--- a/activesupport/lib/active_support/log_subscriber.rb
+++ b/activesupport/lib/active_support/log_subscriber.rb
@@ -48,20 +48,19 @@ module ActiveSupport
mattr_accessor :colorize_logging
self.colorize_logging = true
- class_attribute :logger
-
class << self
- remove_method :logger
def logger
@logger ||= Rails.logger if defined?(Rails)
+ @logger
end
+ attr_writer :logger
+
def attach_to(namespace, log_subscriber=new, notifier=ActiveSupport::Notifications)
log_subscribers << log_subscriber
- @@flushable_loggers = nil
log_subscriber.public_methods(false).each do |event|
- next if :call == event
+ next if %w{ start finish }.include?(event.to_s)
notifier.subscribe("#{event}.#{namespace}", log_subscriber)
end
@@ -71,29 +70,44 @@ module ActiveSupport
@@log_subscribers ||= []
end
- def flushable_loggers
- @@flushable_loggers ||= begin
- loggers = log_subscribers.map(&:logger)
- loggers.uniq!
- loggers.select! { |l| l.respond_to?(:flush) }
- loggers
- end
- end
-
# Flush all log_subscribers' logger.
def flush_all!
- flushable_loggers.each { |log| log.flush }
+ logger.flush if logger.respond_to?(:flush)
end
end
- def call(message, *args)
+ def initialize
+ @queue_key = [self.class.name, object_id].join "-"
+ super
+ end
+
+ def logger
+ LogSubscriber.logger
+ end
+
+ def start(name, id, payload)
return unless logger
- method = message.split('.').first
+ e = ActiveSupport::Notifications::Event.new(name, Time.now, nil, id, payload)
+ parent = event_stack.last
+ parent << e if parent
+
+ event_stack.push e
+ end
+
+ def finish(name, id, payload)
+ return unless logger
+
+ finished = Time.now
+ event = event_stack.pop
+ event.end = finished
+ event.payload.merge!(payload)
+
+ method = name.split('.').first
begin
- send(method, ActiveSupport::Notifications::Event.new(message, *args))
+ send(method, event)
rescue Exception => e
- logger.error "Could not log #{message.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
+ logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
end
end
@@ -118,5 +132,11 @@ module ActiveSupport
bold = bold ? BOLD : ""
"#{bold}#{color}#{text}#{CLEAR}"
end
+
+ private
+
+ def event_stack
+ Thread.current[@queue_key] ||= []
+ end
end
end
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 678f551193..ef1711c60a 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -331,7 +331,7 @@ module ActiveSupport
def load
begin
@codepoints, @composition_exclusion, @composition_map, @boundary, @cp1252 = File.open(self.class.filename, 'rb') { |f| Marshal.load f.read }
- rescue Exception => e
+ rescue => e
raise IOError.new("Couldn't load the Unicode tables for UTF8Handler (#{e.message}), ActiveSupport::Multibyte is unusable")
end
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index 6735c561d3..b4657a8ba9 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -33,7 +33,7 @@ module ActiveSupport
# end
#
# That code returns right away, you are just subscribing to "render" events.
- # The block will be called asynchronously whenever someone instruments "render":
+ # The block is saved and will be called whenever someone instruments "render":
#
# ActiveSupport::Notifications.instrument("render", :extra => :information) do
# render :text => "Foo"
@@ -135,8 +135,6 @@ module ActiveSupport
# to log subscribers in a thread. You can use any queue implementation you want.
#
module Notifications
- @instrumenters = Hash.new { |h,k| h[k] = notifier.listening?(k) }
-
class << self
attr_accessor :notifier
@@ -145,7 +143,7 @@ module ActiveSupport
end
def instrument(name, payload = {})
- if @instrumenters[name]
+ if notifier.listening?(name)
instrumenter.instrument(name, payload) { yield payload if block_given? }
else
yield payload if block_given?
@@ -153,9 +151,7 @@ module ActiveSupport
end
def subscribe(*args, &block)
- notifier.subscribe(*args, &block).tap do
- @instrumenters.clear
- end
+ notifier.subscribe(*args, &block)
end
def subscribed(callback, *args, &block)
@@ -167,7 +163,6 @@ module ActiveSupport
def unsubscribe(args)
notifier.unsubscribe(args)
- @instrumenters.clear
end
def instrumenter
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index 17c99089c1..6ffc091233 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -1,23 +1,34 @@
+require 'mutex_m'
+
module ActiveSupport
module Notifications
# This is a default queue implementation that ships with Notifications.
# It just pushes events to all registered log subscribers.
+ #
+ # This class is thread safe. All methods are reentrant.
class Fanout
+ include Mutex_m
+
def initialize
@subscribers = []
@listeners_for = {}
+ super
end
def subscribe(pattern = nil, block = Proc.new)
subscriber = Subscribers.new pattern, block
- @subscribers << subscriber
- @listeners_for.clear
+ synchronize do
+ @subscribers << subscriber
+ @listeners_for.clear
+ end
subscriber
end
def unsubscribe(subscriber)
- @subscribers.reject! { |s| s.matches?(subscriber) }
- @listeners_for.clear
+ synchronize do
+ @subscribers.reject! { |s| s.matches?(subscriber) }
+ @listeners_for.clear
+ end
end
def start(name, id, payload)
@@ -33,7 +44,9 @@ module ActiveSupport
end
def listeners_for(name)
- @listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) }
+ synchronize do
+ @listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) }
+ end
end
def listening?(name)
@@ -85,9 +98,7 @@ module ActiveSupport
class Timed < Evented
def initialize(pattern, delegate)
- @timestack = Hash.new { |h,id|
- h[id] = Hash.new { |ids,name| ids[name] = [] }
- }
+ @timestack = []
super
end
@@ -96,11 +107,11 @@ module ActiveSupport
end
def start(name, id, payload)
- @timestack[id][name].push Time.now
+ @timestack.push Time.now
end
def finish(name, id, payload)
- started = @timestack[id][name].pop
+ started = @timestack.pop
@delegate.call(name, started, Time.now, id, payload)
end
end
diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb
index 58e292c658..78d0397f1f 100644
--- a/activesupport/lib/active_support/notifications/instrumenter.rb
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -1,3 +1,5 @@
+require 'securerandom'
+
module ActiveSupport
module Notifications
# Instrumentors are stored in a thread local.
@@ -31,7 +33,8 @@ module ActiveSupport
end
class Event
- attr_reader :name, :time, :end, :transaction_id, :payload, :duration
+ attr_reader :name, :time, :transaction_id, :payload, :children
+ attr_accessor :end
def initialize(name, start, ending, transaction_id, payload)
@name = name
@@ -39,12 +42,19 @@ module ActiveSupport
@time = start
@transaction_id = transaction_id
@end = ending
- @duration = 1000.0 * (@end - @time)
+ @children = []
+ end
+
+ def duration
+ 1000.0 * (self.end - time)
+ end
+
+ def <<(event)
+ @children << event
end
def parent_of?(event)
- start = (time - event.time) * 1000
- start <= 0 && (start + duration >= event.duration)
+ @children.include? event
end
end
end
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index 1a0681e850..50ec50ca52 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -33,6 +33,45 @@ module ActiveSupport
end
module Isolation
+ require 'thread'
+
+ class ParallelEach
+ include Enumerable
+
+ # default to 2 cores
+ CORES = (ENV['TEST_CORES'] || 2).to_i
+
+ def initialize list
+ @list = list
+ @queue = SizedQueue.new CORES
+ end
+
+ def grep pattern
+ self.class.new super
+ end
+
+ def each
+ threads = CORES.times.map {
+ Thread.new {
+ while job = @queue.pop
+ yield job
+ end
+ }
+ }
+ @list.each { |i| @queue << i }
+ CORES.times { @queue << nil }
+ threads.each(&:join)
+ end
+ end
+
+ def self.included klass
+ klass.extend(Module.new {
+ def test_methods
+ ParallelEach.new super
+ end
+ })
+ end
+
def self.forking_env?
!ENV["NO_FORK"] && ((RbConfig::CONFIG['host_os'] !~ /mswin|mingw/) && (RUBY_PLATFORM !~ /java/))
end
diff --git a/activesupport/test/clean_backtrace_test.rb b/activesupport/test/clean_backtrace_test.rb
index 32e346bb48..b14950acb3 100644
--- a/activesupport/test/clean_backtrace_test.rb
+++ b/activesupport/test/clean_backtrace_test.rb
@@ -6,8 +6,10 @@ class BacktraceCleanerFilterTest < ActiveSupport::TestCase
@bc.add_filter { |line| line.gsub("/my/prefix", '') }
end
- test "backtrace should not contain prefix when it has been filtered out" do
- assert_equal "/my/class.rb", @bc.clean([ "/my/prefix/my/class.rb" ]).first
+ test "backtrace should filter all lines in a backtrace, removing prefixes" do
+ assert_equal \
+ ["/my/class.rb", "/my/module.rb"],
+ @bc.clean(["/my/prefix/my/class.rb", "/my/prefix/my/module.rb"])
end
test "backtrace cleaner should allow removing filters" do
@@ -19,11 +21,6 @@ class BacktraceCleanerFilterTest < ActiveSupport::TestCase
assert_equal "/my/other_prefix/my/class.rb", @bc.clean([ "/my/other_prefix/my/class.rb" ]).first
end
- test "backtrace should filter all lines in a backtrace" do
- assert_equal \
- ["/my/class.rb", "/my/module.rb"],
- @bc.clean([ "/my/prefix/my/class.rb", "/my/prefix/my/module.rb" ])
- end
end
class BacktraceCleanerSilencerTest < ActiveSupport::TestCase
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index e14a137f84..088b74a29a 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -289,6 +289,18 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
assert_equal Date.new(2004, 2, 29), Date.new(2004, 3, 31).last_month
end
+ def test_next_quarter_on_31st
+ assert_equal Date.new(2005, 11, 30), Date.new(2005, 8, 31).next_quarter
+ end
+
+ def test_prev_quarter_on_31st
+ assert_equal Date.new(2004, 2, 29), Date.new(2004, 5, 31).prev_quarter
+ end
+
+ def test_last_quarter_on_31st
+ assert_equal Date.new(2004, 2, 29), Date.new(2004, 5, 31).last_quarter
+ end
+
def test_yesterday_constructor
assert_equal Date.current - 1, Date.yesterday
end
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index 3da0825489..183d58482d 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -271,6 +271,18 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 3, 31).last_month
end
+ def test_next_quarter_on_31st
+ assert_equal DateTime.civil(2005, 11, 30), DateTime.civil(2005, 8, 31).next_quarter
+ end
+
+ def test_prev_quarter_on_31st
+ assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 5, 31).prev_quarter
+ end
+
+ def test_last_quarter_on_31st
+ assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 5, 31).last_quarter
+ end
+
def test_xmlschema
assert_match(/^1880-02-28T15:15:10\+00:?00$/, DateTime.civil(1880, 2, 28, 15, 15, 10).xmlschema)
assert_match(/^1980-02-28T15:15:10\+00:?00$/, DateTime.civil(1980, 2, 28, 15, 15, 10).xmlschema)
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 5d422ce5ad..4dc9f57038 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -457,6 +457,13 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal '1234', roundtrip.default
end
+ def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_access
+ hash = HashWithIndifferentAccess.new {|h, k| h[k] = []}
+ hash[:a] << 1
+
+ assert_equal [1], hash[:a]
+ end
+
def test_indifferent_hash_with_array_of_hashes
hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] }}.with_indifferent_access
assert_equal "1", hash[:urls][:url].first[:address]
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index 15c04bedf7..d6f285598e 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -906,4 +906,17 @@ class TimeExtMarshalingTest < ActiveSupport::TestCase
assert_equal t.to_f, unmarshaled.to_f
assert_equal t, unmarshaled
end
+
+
+ def test_next_quarter_on_31st
+ assert_equal Time.local(2005, 11, 30), Time.local(2005, 8, 31).next_quarter
+ end
+
+ def test_prev_quarter_on_31st
+ assert_equal Time.local(2004, 2, 29), Time.local(2004, 5, 31).prev_quarter
+ end
+
+ def test_last_quarter_on_31st
+ assert_equal Time.local(2004, 2, 29), Time.local(2004, 5, 31).last_quarter
+ end
end
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 91ae6bc189..1f32e4ff92 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -413,6 +413,16 @@ class InflectorTest < ActiveSupport::TestCase
end
end
+ Irregularities.each do |irregularity|
+ singular, plural = *irregularity
+ ActiveSupport::Inflector.inflections do |inflect|
+ define_method("test_singularize_of_irregularity_#{singular}_should_be_the_same") do
+ inflect.irregular(singular, plural)
+ assert_equal singular, ActiveSupport::Inflector.singularize(singular)
+ end
+ end
+ end
+
[ :all, [] ].each do |scope|
ActiveSupport::Inflector.inflections do |inflect|
define_method("test_clear_inflections_with_#{scope.kind_of?(Array) ? "no_arguments" : scope}") do
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index 9fa1f417e4..ca4efd2e59 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -308,5 +308,7 @@ module InflectorTestCases
'child' => 'children',
'sex' => 'sexes',
'move' => 'moves',
+ 'cow' => 'kine',
+ 'zombie' => 'zombies',
}
end
diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb
index fc9fa90d07..bcb393c7bc 100644
--- a/activesupport/test/notifications_test.rb
+++ b/activesupport/test/notifications_test.rb
@@ -221,13 +221,15 @@ module Notifications
assert_equal Hash[:payload => :bar], event.payload
end
- def test_event_is_parent_based_on_time_frame
+ def test_event_is_parent_based_on_children
time = Time.utc(2009, 01, 01, 0, 0, 1)
parent = event(:foo, Time.utc(2009), Time.utc(2009) + 100, random_id, {})
child = event(:foo, time, time + 10, random_id, {})
not_child = event(:foo, time, time + 100, random_id, {})
+ parent.children << child
+
assert parent.parent_of?(child)
assert !child.parent_of?(parent)
assert !parent.parent_of?(not_child)
diff --git a/guides/source/active_record_querying.textile b/guides/source/active_record_querying.textile
index 4b14671efc..101988c59e 100644
--- a/guides/source/active_record_querying.textile
+++ b/guides/source/active_record_querying.textile
@@ -609,8 +609,8 @@ And this will give you a single +Order+ object for each date where there are ord
The SQL that would be executed would be something like this:
<sql>
-SELECT date(created_at) as ordered_date, sum(price) as total_price
-FROM orders
+SELECT date(created_at) as ordered_date, sum(price) as total_price
+FROM orders
GROUP BY date(created_at)
</sql>
@@ -627,9 +627,9 @@ Order.select("date(created_at) as ordered_date, sum(price) as total_price").grou
The SQL that would be executed would be something like this:
<sql>
-SELECT date(created_at) as ordered_date, sum(price) as total_price
-FROM orders
-GROUP BY date(created_at)
+SELECT date(created_at) as ordered_date, sum(price) as total_price
+FROM orders
+GROUP BY date(created_at)
HAVING sum(price) > 100
</sql>
@@ -1286,26 +1286,36 @@ Client.connection.select_all("SELECT * FROM clients WHERE id = '1'")
h3. +pluck+
-<tt>pluck</tt> can be used to query a single column from the underlying table of a model. It accepts a column name as argument and returns an array of values of the specified column with the corresponding data type.
+<tt>pluck</tt> can be used to query a single or multiple columns from the underlying table of a model. It accepts a list of column names as argument and returns an array of values of the specified columns with the corresponding data type.
<ruby>
Client.where(:active => true).pluck(:id)
# SELECT id FROM clients WHERE active = 1
+# => [1, 2, 3]
Client.uniq.pluck(:role)
# SELECT DISTINCT role FROM clients
+# => ['admin', 'member', 'guest']
+
+Client.pluck(:id, :name)
+# SELECT clients.id, clients.name FROM clients
+# => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
</ruby>
+pluck+ makes it possible to replace code like
<ruby>
Client.select(:id).map { |c| c.id }
+# or
+Client.select(:id).map { |c| [c.id, c.name] }
</ruby>
with
<ruby>
Client.pluck(:id)
+# or
+Client.pluck(:id, :name)
</ruby>
h3. +ids+
diff --git a/guides/source/active_support_core_extensions.textile b/guides/source/active_support_core_extensions.textile
index ad3024d30d..99b8bdd3fd 100644
--- a/guides/source/active_support_core_extensions.textile
+++ b/guides/source/active_support_core_extensions.textile
@@ -1859,12 +1859,18 @@ Enables the formatting of numbers in a variety of ways.
Produce a string representation of a number as a telephone number:
<ruby>
-5551234.to_s(:phone) # => 555-1234
-1235551234.to_s(:phone) # => 123-555-1234
-1235551234.to_s(:phone, :area_code => true) # => (123) 555-1234
-1235551234.to_s(:phone, :delimiter => " ") # => 123 555 1234
-1235551234.to_s(:phone, :area_code => true, :extension => 555) # => (123) 555-1234 x 555
-1235551234.to_s(:phone, :country_code => 1) # => +1-123-555-1234
+5551234.to_s(:phone)
+# => 555-1234
+1235551234.to_s(:phone)
+# => 123-555-1234
+1235551234.to_s(:phone, :area_code => true)
+# => (123) 555-1234
+1235551234.to_s(:phone, :delimiter => " ")
+# => 123 555 1234
+1235551234.to_s(:phone, :area_code => true, :extension => 555)
+# => (123) 555-1234 x 555
+1235551234.to_s(:phone, :country_code => 1)
+# => +1-123-555-1234
</ruby>
Produce a string representation of a number as currency:
@@ -1878,10 +1884,14 @@ Produce a string representation of a number as currency:
Produce a string representation of a number as a percentage:
<ruby>
-100.to_s(:percentage) # => 100.000%
-100.to_s(:percentage, :precision => 0) # => 100%
-1000.to_s(:percentage, :delimiter => '.', :separator => ',') # => 1.000,000%
-302.24398923423.to_s(:percentage, :precision => 5) # => 302.24399%
+100.to_s(:percentage)
+# => 100.000%
+100.to_s(:percentage, :precision => 0)
+# => 100%
+1000.to_s(:percentage, :delimiter => '.', :separator => ',')
+# => 1.000,000%
+302.24398923423.to_s(:percentage, :precision => 5)
+# => 302.24399%
</ruby>
Produce a string representation of a number in delimited form:
@@ -1897,34 +1907,34 @@ Produce a string representation of a number in delimited form:
Produce a string representation of a number rounded to a precision:
<ruby>
-111.2345.to_s(:rounded) # => 111.235
-111.2345.to_s(:rounded, :precision => 2) # => 111.23
-13.to_s(:rounded, :precision => 5) # => 13.00000
-389.32314.to_s(:rounded, :precision => 0) # => 389
-111.2345.to_s(:rounded, :significant => true) # => 111
+111.2345.to_s(:rounded) # => 111.235
+111.2345.to_s(:rounded, :precision => 2) # => 111.23
+13.to_s(:rounded, :precision => 5) # => 13.00000
+389.32314.to_s(:rounded, :precision => 0) # => 389
+111.2345.to_s(:rounded, :significant => true) # => 111
</ruby>
Produce a string representation of a number as a human-readable number of bytes:
<ruby>
-123.to_s(:human_size) # => 123 Bytes
-1234.to_s(:human_size) # => 1.21 KB
-12345.to_s(:human_size) # => 12.1 KB
-1234567.to_s(:human_size) # => 1.18 MB
-1234567890.to_s(:human_size) # => 1.15 GB
-1234567890123.to_s(:human_size) # => 1.12 TB
+123.to_s(:human_size) # => 123 Bytes
+1234.to_s(:human_size) # => 1.21 KB
+12345.to_s(:human_size) # => 12.1 KB
+1234567.to_s(:human_size) # => 1.18 MB
+1234567890.to_s(:human_size) # => 1.15 GB
+1234567890123.to_s(:human_size) # => 1.12 TB
</ruby>
Produce a string representation of a number in human-readable words:
<ruby>
-123.to_s(:human) # => "123"
-1234.to_s(:human) # => "1.23 Thousand"
-12345.to_s(:human) # => "12.3 Thousand"
-1234567.to_s(:human) # => "1.23 Million"
-1234567890.to_s(:human) # => "1.23 Billion"
-1234567890123.to_s(:human) # => "1.23 Trillion"
-1234567890123456.to_s(:human) # => "1.23 Quadrillion"
+123.to_s(:human) # => "123"
+1234.to_s(:human) # => "1.23 Thousand"
+12345.to_s(:human) # => "12.3 Thousand"
+1234567.to_s(:human) # => "1.23 Million"
+1234567890.to_s(:human) # => "1.23 Billion"
+1234567890123.to_s(:human) # => "1.23 Trillion"
+1234567890123456.to_s(:human) # => "1.23 Quadrillion"
</ruby>
NOTE: Defined in +active_support/core_ext/numeric/formatting.rb+.
@@ -3035,6 +3045,27 @@ Date.new(2000, 1, 31).next_month # => Tue, 29 Feb 2000
+prev_month+ is aliased to +last_month+.
+h6. +prev_quarter+, +next_quarter+
+
+Same as +prev_month+ and +next_month+. It returns the date with the same day in the previous or next quarter:
+
+<ruby>
+t = Time.local(2010, 5, 8) # => Sat, 08 May 2010
+t.prev_quarter # => Mon, 08 Feb 2010
+t.next_quarter # => Sun, 08 Aug 2010
+</ruby>
+
+If such a day does not exist, the last day of the corresponding month is returned:
+
+<ruby>
+Time.local(2000, 7, 31).prev_quarter # => Sun, 30 Apr 2000
+Time.local(2000, 5, 31).prev_quarter # => Tue, 29 Feb 2000
+Time.local(2000, 10, 31).prev_quarter # => Mon, 30 Oct 2000
+Time.local(2000, 11, 31).next_quarter # => Wed, 28 Feb 2001
+</ruby>
+
++prev_quarter+ is aliased to +last_quarter+.
+
h6. +beginning_of_week+, +end_of_week+
The methods +beginning_of_week+ and +end_of_week+ return the dates for the
diff --git a/guides/source/rails_on_rack.textile b/guides/source/rails_on_rack.textile
index 3a7c392508..63712b22ef 100644
--- a/guides/source/rails_on_rack.textile
+++ b/guides/source/rails_on_rack.textile
@@ -195,6 +195,23 @@ use Rack::Runtime
run Blog::Application.routes
</shell>
+If you want to remove session related middleware, do the following:
+
+<ruby>
+# config/application.rb
+config.middleware.delete "ActionDispatch::Cookies"
+config.middleware.delete "ActionDispatch::Session::CookieStore"
+config.middleware.delete "ActionDispatch::Flash"
+</ruby>
+
+And to remove browser related middleware,
+
+<ruby>
+# config/application.rb
+config.middleware.delete "ActionDispatch::BestStandardsSupport"
+config.middleware.delete "Rack::MethodOverride"
+</ruby>
+
h4. Internal Middleware Stack
Much of Action Controller's functionality is implemented as Middlewares. The following list explains the purpose of each of them:
diff --git a/guides/source/routing.textile b/guides/source/routing.textile
index 7941e655bb..dae25853cd 100644
--- a/guides/source/routing.textile
+++ b/guides/source/routing.textile
@@ -644,6 +644,14 @@ You should put the +root+ route at the top of the file, because it is the most p
NOTE: The +root+ route only routes +GET+ requests to the action.
+h4. Unicode character routes
+
+You can specify unicode character routes directly. For example
+
+<ruby>
+match 'こんにちは' => 'welcome#index'
+</ruby>
+
h3. Customizing Resourceful Routes
While the default routes and helpers generated by +resources :posts+ will usually serve you well, you may want to customize them in some way. Rails allows you to customize virtually any generic part of the resourceful helpers.
diff --git a/guides/source/upgrading_ruby_on_rails.textile b/guides/source/upgrading_ruby_on_rails.textile
index 6cdc6ab289..4bf4751127 100644
--- a/guides/source/upgrading_ruby_on_rails.textile
+++ b/guides/source/upgrading_ruby_on_rails.textile
@@ -50,6 +50,8 @@ h4(#action_pack4_0). Action Pack
Rails 4.0 changed how <tt>assert_generates</tt>, <tt>assert_recognizes</tt>, and <tt>assert_routing</tt> work. Now all these assertions raise <tt>Assertion</tt> instead of <tt>ActionController::RoutingError</tt>.
+Rails 4.0 also changed the way unicode character routes are drawn. Now you can draw unicode character routes directly. If you already draw such routes, you must change them, e.g. <tt>get Rack::Utils.escape('こんにちは'), :controller => 'welcome', :action => 'index'</tt> to <tt>get 'こんにちは', :controller => 'welcome', :action => 'index'</tt>.
+
h4(#helpers_order). Helpers Loading Order
The loading order of helpers from more than one directory has changed in Rails 4.0. Previously, helpers from all directories were gathered and then sorted alphabetically. After upgrade to Rails 4.0 helpers will preserve the order of loaded directories and will be sorted alphabetically only within each directory. Unless you explicitly use <tt>helpers_path</tt> parameter, this change will only impact the way of loading helpers from engines. If you rely on the fact that particular helper from engine loads before or after another helper from application or another engine, you should check if correct methods are available after upgrade. If you would like to change order in which engines are loaded, you can use <tt>config.railties_order=</tt> method.
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index 28d7680669..a49a1724c5 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -20,6 +20,7 @@ module Rails
include Rails::Generators::Actions
add_runtime_options!
+ strict_args_position!
# Returns the source root for this generator using default_source_root as default.
def self.source_root(path=nil)
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml
index fe9466b366..e1a00d076f 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml
@@ -22,8 +22,8 @@ development:
# Minimum log levels, in increasing order:
# debug5, debug4, debug3, debug2, debug1,
# log, notice, warning, error, fatal, and panic
- # The server defaults to notice.
- #min_messages: warning
+ # Defaults to warning.
+ #min_messages: notice
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
index 467a4e725f..07223a71c9 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
@@ -35,8 +35,8 @@ development:
# Minimum log levels, in increasing order:
# debug5, debug4, debug3, debug2, debug1,
# log, notice, warning, error, fatal, and panic
- # The server defaults to notice.
- #min_messages: warning
+ # Defaults to warning.
+ #min_messages: notice
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb
index ff9cf0087e..2ff340755a 100644
--- a/railties/lib/rails/generators/test_case.rb
+++ b/railties/lib/rails/generators/test_case.rb
@@ -37,8 +37,16 @@ module Rails
self.current_path = File.expand_path(Dir.pwd)
self.default_arguments = []
- setup :destination_root_is_set?, :ensure_current_path
- teardown :ensure_current_path
+ def setup
+ destination_root_is_set?
+ ensure_current_path
+ super
+ end
+
+ def teardown
+ ensure_current_path
+ super
+ end
# Sets which generator should be tested:
#
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index 2a39826c8d..207739c4bc 100644
--- a/railties/railties.gemspec
+++ b/railties/railties.gemspec
@@ -22,11 +22,7 @@ Gem::Specification.new do |s|
s.rdoc_options << '--exclude' << '.'
s.add_dependency('rake', '>= 0.8.7')
-
- # The current API of the Thor gem (0.14) will remain stable at least until Thor 2.0. Because
- # Thor is so heavily used by other gems, we will accept Thor's semver guarantee to reduce
- # the possibility of conflicts.
- s.add_dependency('thor', '>= 0.14.6', '< 2.0')
+ s.add_dependency('thor', '>= 0.15.3', '< 2.0')
s.add_dependency('rdoc', '~> 3.4')
s.add_dependency('activesupport', version)
s.add_dependency('actionpack', version)
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 7193dfdef5..c1aa98e481 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -148,6 +148,15 @@ module ApplicationTests
assert AppTemplate::Application.config.allow_concurrency
end
+ test "initialize a threadsafe app" do
+ add_to_config <<-RUBY
+ config.threadsafe!
+ RUBY
+
+ require "#{app_path}/config/application"
+ assert AppTemplate::Application.initialize!
+ end
+
test "asset_path defaults to nil for application" do
require "#{app_path}/config/environment"
assert_equal nil, AppTemplate::Application.config.asset_path
@@ -179,7 +188,7 @@ module ApplicationTests
require "#{app_path}/config/environment"
- assert !ActionController.autoload?(:Caching)
+ assert !ActionView.autoload?(:AssetPaths)
end
test "filter_parameters should be able to set via config.filter_parameters" do
@@ -374,9 +383,10 @@ module ApplicationTests
require "#{app_path}/config/environment"
- assert_equal ActiveModel::MassAssignmentSecurity::WhiteList,
- ActiveRecord::Base.active_authorizers[:default].class
- assert_equal [], ActiveRecord::Base.active_authorizers[:default].to_a
+ klass = Class.new(ActiveRecord::Base)
+
+ assert_equal ActiveModel::MassAssignmentSecurity::WhiteList, klass.active_authorizers[:default].class
+ assert_equal [], klass.active_authorizers[:default].to_a
end
test "registers interceptors with ActionMailer" do
@@ -617,5 +627,26 @@ module ApplicationTests
make_basic_app
assert app.config.colorize_logging
end
+
+ test "config.active_record.observers" do
+ add_to_config <<-RUBY
+ config.active_record.observers = :foo_observer
+ RUBY
+
+ app_file 'app/models/foo.rb', <<-RUBY
+ class Foo < ActiveRecord::Base
+ end
+ RUBY
+
+ app_file 'app/models/foo_observer.rb', <<-RUBY
+ class FooObserver < ActiveRecord::Observer
+ end
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ ActiveRecord::Base
+ assert defined?(FooObserver)
+ end
end
end
diff --git a/railties/test/application/rack/logger_test.rb b/railties/test/application/rack/logger_test.rb
index a77c6f472c..46fd09cb26 100644
--- a/railties/test/application/rack/logger_test.rb
+++ b/railties/test/application/rack/logger_test.rb
@@ -5,6 +5,7 @@ require "rack/test"
module ApplicationTests
module RackTests
class LoggerTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
include ActiveSupport::LogSubscriber::TestHelper
include Rack::Test::Methods
@@ -17,6 +18,7 @@ module ApplicationTests
end
def teardown
+ super
teardown_app
end
diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb
index 3f4db77897..27de75b63b 100644
--- a/railties/test/application/rake/notes_test.rb
+++ b/railties/test/application/rake/notes_test.rb
@@ -3,12 +3,16 @@ require "isolation/abstract_unit"
module ApplicationTests
module RakeTests
class RakeNotesTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
def setup
build_app
require "rails/all"
+ super
end
def teardown
+ super
teardown_app
end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index eac471a07e..e0ee349550 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -122,6 +122,18 @@ module ApplicationTests
assert_equal 0, ::AppTemplate::Application::User.count
end
+ def test_loading_only_yml_fixtures
+ Dir.chdir(app_path) do
+ `rake db:migrate`
+ end
+
+ app_file "test/fixtures/products.csv", ""
+
+ require "#{rails_root}/config/environment"
+ errormsg = Dir.chdir(app_path) { `rake db:fixtures:load` }
+ assert $?.success?, errormsg
+ end
+
def test_scaffold_tests_pass_by_default
output = Dir.chdir(app_path) do
`rails generate scaffold user username:string password:string;
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 03be81e59f..800b1c90f0 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -19,15 +19,17 @@ RAILS_FRAMEWORK_ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../..")
# to run the tests
require "active_support/testing/isolation"
require "active_support/core_ext/kernel/reporting"
+require 'tmpdir'
module TestHelpers
module Paths
- module_function
-
- TMP_PATH = File.expand_path(File.join(File.dirname(__FILE__), *%w[.. .. tmp]))
+ def app_template_path
+ File.join Dir.tmpdir, 'app_template'
+ end
def tmp_path(*args)
- File.join(TMP_PATH, *args)
+ @tmp_path ||= File.realpath(Dir.mktmpdir)
+ File.join(@tmp_path, *args)
end
def app_path(*args)
@@ -95,7 +97,7 @@ module TestHelpers
ENV['RAILS_ENV'] = 'development'
FileUtils.rm_rf(app_path)
- FileUtils.cp_r(tmp_path('app_template'), app_path)
+ FileUtils.cp_r(app_template_path, app_path)
# Delete the initializers unless requested
unless options[:initializers]
@@ -272,18 +274,18 @@ end
Module.new do
extend TestHelpers::Paths
# Build a rails app
- if File.exist?(tmp_path)
- FileUtils.rm_rf(tmp_path)
+ if File.exist?(app_template_path)
+ FileUtils.rm_rf(app_template_path)
end
- FileUtils.mkdir(tmp_path)
+ FileUtils.mkdir(app_template_path)
environment = File.expand_path('../../../../load_paths', __FILE__)
if File.exist?("#{environment}.rb")
require_environment = "-r #{environment}"
end
- `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/bin/rails new #{tmp_path('app_template')}`
- File.open("#{tmp_path}/app_template/config/boot.rb", 'w') do |f|
+ `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/bin/rails new #{app_template_path}`
+ File.open("#{app_template_path}/config/boot.rb", 'w') do |f|
if require_environment
f.puts "Dir.chdir('#{File.dirname(environment)}') do"
f.puts " require '#{environment}'"
diff --git a/railties/test/railties/generators_test.rb b/railties/test/railties/generators_test.rb
index 6ebbabc0ff..1938bfb6c2 100644
--- a/railties/test/railties/generators_test.rb
+++ b/railties/test/railties/generators_test.rb
@@ -8,11 +8,13 @@ module RailtiesTests
class GeneratorTest < Rails::Generators::TestCase
include ActiveSupport::Testing::Isolation
- TMP_PATH = File.expand_path(File.join(File.dirname(__FILE__), *%w[.. .. tmp]))
- self.destination_root = File.join(TMP_PATH, "foo_bar")
+ def destination_root
+ tmp_path 'foo_bar'
+ end
def tmp_path(*args)
- File.join(TMP_PATH, *args)
+ @tmp_path ||= File.realpath(Dir.mktmpdir)
+ File.join(@tmp_path, *args)
end
def engine_path