diff options
227 files changed, 3076 insertions, 2662 deletions
@@ -14,7 +14,7 @@ gem 'jquery-rails' if ENV['JOURNEY'] gem 'journey', :path => ENV['JOURNEY'] else - gem 'journey' + gem 'journey', :git => "git://github.com/rails/journey" end # This needs to be with require false to avoid diff --git a/README.rdoc b/README.rdoc index 8844c74175..dc8805245b 100644 --- a/README.rdoc +++ b/README.rdoc @@ -73,5 +73,7 @@ to proceed. {Join us}[http://contributors.rubyonrails.org]! == License -Ruby on Rails is released under the MIT license. +Ruby on Rails is released under the MIT license: + +* http://www.opensource.org/licenses/MIT diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc index dc74b590f7..4c2516db59 100644 --- a/actionmailer/README.rdoc +++ b/actionmailer/README.rdoc @@ -42,7 +42,7 @@ So the corresponding body template for the method above could look like this: Thank you for signing up! -And if the recipient was given as "david@loudthinking.com", the email +If the recipient was given as "david@loudthinking.com", the email generated would look like this: Date: Mon, 25 Jan 2010 22:48:09 +1100 @@ -62,7 +62,7 @@ generated would look like this: Thank you for signing up! In previous version of Rails you would call <tt>create_method_name</tt> and -<tt>deliver_method_name</tt>. Rails 3.0 has a much simpler interface, you +<tt>deliver_method_name</tt>. Rails 3.0 has a much simpler interface - you simply call the method and optionally call +deliver+ on the return value. Calling the method returns a Mail Message object: @@ -76,7 +76,7 @@ Or you can just chain the methods together like: == Setting defaults -It is possible to set default values that will be used in every method in your Action Mailer class. To implement this functionality, you just call the public class method <tt>default</tt> which you get for free from ActionMailer::Base. This method accepts a Hash as the parameter. You can use any of the headers e-mail messages has, like <tt>:from</tt> as the key. You can also pass in a string as the key, like "Content-Type", but Action Mailer does this out of the box for you, so you won't need to worry about that. Finally it is also possible to pass in a Proc that will get evaluated when it is needed. +It is possible to set default values that will be used in every method in your Action Mailer class. To implement this functionality, you just call the public class method <tt>default</tt> which you get for free from ActionMailer::Base. This method accepts a Hash as the parameter. You can use any of the headers e-mail messages has, like <tt>:from</tt> as the key. You can also pass in a string as the key, like "Content-Type", but Action Mailer does this out of the box for you, so you won't need to worry about that. Finally, it is also possible to pass in a Proc that will get evaluated when it is needed. Note that every value you set with this method will get over written if you use the same key in your mailer method. @@ -148,7 +148,9 @@ Source code can be downloaded as part of the Rails project on GitHub == License -Action Mailer is released under the MIT license. +Action Mailer is released under the MIT license: + +* http://www.opensource.org/licenses/MIT == Support diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb index 9bd73dd740..2ac4f3f681 100644 --- a/actionmailer/lib/action_mailer.rb +++ b/actionmailer/lib/action_mailer.rb @@ -31,7 +31,6 @@ require 'action_mailer/version' # Common Active Support usage in Action Mailer require 'active_support/core_ext/class' require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/array/uniq_by' require 'active_support/core_ext/module/attr_internal' require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/string/inflections' diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 71c156f1a2..2f9fff0f97 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -708,7 +708,7 @@ module ActionMailer #:nodoc: def each_template(paths, name, &block) #:nodoc: templates = lookup_context.find_all(name, Array.wrap(paths)) - templates.uniq_by { |t| t.formats }.each(&block) + templates.uniq { |t| t.formats }.each(&block) end def create_parts_from_responses(m, responses) #:nodoc: diff --git a/actionmailer/lib/rails/generators/mailer/USAGE b/actionmailer/lib/rails/generators/mailer/USAGE index 448ddbd5df..9f1d6b182e 100644 --- a/actionmailer/lib/rails/generators/mailer/USAGE +++ b/actionmailer/lib/rails/generators/mailer/USAGE @@ -1,6 +1,6 @@ Description: ============ - Stubs out a new mailer and its views. Pass the mailer name, either + Stubs out a new mailer and its views. Passes the mailer name, either CamelCased or under_scored, and an optional list of emails as arguments. This generates a mailer class in app/mailers and invokes your template diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb index 0b076e1ff9..d86e0dc4c0 100644 --- a/actionmailer/test/abstract_unit.rb +++ b/actionmailer/test/abstract_unit.rb @@ -13,14 +13,11 @@ end require 'active_support/core_ext/kernel/reporting' -require 'active_support/core_ext/string/encoding' -if "ruby".encoding_aware? - # These are the normal settings that will be set up by Railties - # TODO: Have these tests support other combinations of these values - silence_warnings do - Encoding.default_internal = "UTF-8" - Encoding.default_external = "UTF-8" - end +# These are the normal settings that will be set up by Railties +# TODO: Have these tests support other combinations of these values +silence_warnings do + Encoding.default_internal = "UTF-8" + Encoding.default_external = "UTF-8" end lib = File.expand_path("#{File.dirname(__FILE__)}/../lib") diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index 9fdd0e1ced..65550ab505 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -107,7 +107,7 @@ class BaseTest < ActiveSupport::TestCase assert_equal(1, email.attachments.length) assert_equal('invoice.jpg', email.attachments[0].filename) expected = "\312\213\254\232)b" - expected.force_encoding(Encoding::BINARY) if '1.9'.respond_to?(:force_encoding) + expected.force_encoding(Encoding::BINARY) assert_equal expected, email.attachments['invoice.jpg'].decoded end @@ -116,7 +116,7 @@ class BaseTest < ActiveSupport::TestCase assert_equal(1, email.attachments.length) assert_equal('invoice.jpg', email.attachments[0].filename) expected = "\312\213\254\232)b" - expected.force_encoding(Encoding::BINARY) if '1.9'.respond_to?(:force_encoding) + expected.force_encoding(Encoding::BINARY) assert_equal expected, email.attachments['invoice.jpg'].decoded end diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index b753addef4..0c1228b7c6 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,7 @@ ## Rails 3.2.0 (unreleased) ## +* Add :gzip option to `caches_page`. The default option can be configured globally using `page_cache_compression` *Andrey Sitnik* + * The ShowExceptions middleware now accepts a exceptions application that is responsible to render an exception when the application fails. The application is invoked with a copy of the exception in `env["action_dispatch.exception"]` and with the PATH_INFO rewritten to the status code. *José Valim* * Add `button_tag` support to ActionView::Helpers::FormBuilder. diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc index 95301c21ee..fc36423f83 100644 --- a/actionpack/README.rdoc +++ b/actionpack/README.rdoc @@ -327,7 +327,9 @@ Source code can be downloaded as part of the Rails project on GitHub == License -Action Pack is released under the MIT license. +Action Pack is released under the MIT license: + +* http://www.opensource.org/licenses/MIT == Support diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index d95770c049..9019c07bca 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -35,7 +35,7 @@ module AbstractController include AbstractController::ViewPaths included do - config_accessor :protected_instance_variables, :instance_reader => false + class_attribute :protected_instance_variables self.protected_instance_variables = [] end @@ -59,12 +59,6 @@ module AbstractController attr_internal_writer :view_context_class - # Explicitly define protected_instance_variables so it can be - # inherited and overwritten by other modules if needed. - def protected_instance_variables - config.protected_instance_variables - end - def view_context_class @_view_context_class ||= self.class.view_context_class end diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb index 957bb7de6b..159f718029 100644 --- a/actionpack/lib/action_controller/caching/pages.rb +++ b/actionpack/lib/action_controller/caching/pages.rb @@ -16,7 +16,7 @@ module ActionController #:nodoc: # caches_page :show, :new # end # - # This will generate cache files such as <tt>weblog/show/5.html</tt> and <tt>weblog/new.html</tt>, which match the URLs used + # This will generate cache files such as <tt>weblog/show/5.html</tt> and <tt>weblog/new.html</tt>, which match the URLs used # that would normally trigger dynamic page generation. Page caching works by configuring a web server to first check for the # existence of files on disk, and to serve them directly when found, without passing the request through to Action Pack. # This is much faster than handling the full dynamic request in the usual way. @@ -38,23 +38,25 @@ module ActionController #:nodoc: extend ActiveSupport::Concern included do - ## - # :singleton-method: # The cache directory should be the document root for the web server and is set using <tt>Base.page_cache_directory = "/document/root"</tt>. # For Rails, this directory has already been set to Rails.public_path (which is usually set to <tt>Rails.root + "/public"</tt>). Changing # this setting can be useful to avoid naming conflicts with files in <tt>public/</tt>, but doing so will likely require configuring your # web server to look in the new location for cached files. - config_accessor :page_cache_directory + class_attribute :page_cache_directory self.page_cache_directory ||= '' - ## - # :singleton-method: # Most Rails requests do not have an extension, such as <tt>/weblog/new</tt>. In these cases, the page caching mechanism will add one in # order to make it easy for the cached files to be picked up properly by the web server. By default, this cache extension is <tt>.html</tt>. # If you want something else, like <tt>.php</tt> or <tt>.shtml</tt>, just set Base.page_cache_extension. In cases where a request already has an # extension, such as <tt>.xml</tt> or <tt>.rss</tt>, page caching will not add an extension. This allows it to work well with RESTful apps. - config_accessor :page_cache_extension + class_attribute :page_cache_extension self.page_cache_extension ||= '.html' + + # The compression used for gzip. If false (default), the page is not compressed. + # If can be a symbol showing the ZLib compression method, for example, :best_compression + # or :best_speed or an integer configuring the compression level. + class_attribute :page_cache_compression + self.page_cache_compression ||= false end module ClassMethods @@ -66,24 +68,31 @@ module ActionController #:nodoc: instrument_page_cache :expire_page, path do File.delete(path) if File.exist?(path) + File.delete(path + '.gz') if File.exist?(path + '.gz') end end # Manually cache the +content+ in the key determined by +path+. Example: # cache_page "I'm the cached content", "/lists/show" - def cache_page(content, path, extension = nil) + def cache_page(content, path, extension = nil, gzip = Zlib::BEST_COMPRESSION) return unless perform_caching path = page_cache_path(path, extension) instrument_page_cache :write_page, path do FileUtils.makedirs(File.dirname(path)) File.open(path, "wb+") { |f| f.write(content) } + if gzip + Zlib::GzipWriter.open(path + '.gz', gzip) { |f| f.write(content) } + end end end - # Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that + # Caches the +actions+ using the page-caching approach that'll store + # the cache in a path within the page_cache_directory that # matches the triggering url. # + # You can also pass a :gzip option to override the class configuration one. + # # Usage: # # # cache the index action @@ -91,10 +100,28 @@ module ActionController #:nodoc: # # # cache the index action except for JSON requests # caches_page :index, :if => Proc.new { |c| !c.request.format.json? } + # + # # don't gzip images + # caches_page :image, :gzip => false def caches_page(*actions) return unless perform_caching options = actions.extract_options! - after_filter({:only => actions}.merge(options)) { |c| c.cache_page } + + gzip_level = options.fetch(:gzip, page_cache_compression) + gzip_level = case gzip_level + when Symbol + Zlib.const_get(gzip_level.to_s.upcase) + when Fixnum + gzip_level + when false + nil + else + Zlib::BEST_COMPRESSION + end + + after_filter({:only => actions}.merge(options)) do |c| + c.cache_page(nil, nil, gzip_level) + end end private @@ -136,7 +163,7 @@ module ActionController #:nodoc: # Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used. # If no options are provided, the url of the current request being handled is used. Example: # cache_page "I'm the cached content", :controller => "lists", :action => "show" - def cache_page(content = nil, options = nil) + def cache_page(content = nil, options = nil, gzip = Zlib::BEST_COMPRESSION) return unless self.class.perform_caching && caching_allowed? path = case options @@ -152,7 +179,7 @@ module ActionController #:nodoc: extension = ".#{type_symbol}" end - self.class.cache_page(content || response.body, path, extension) + self.class.cache_page(content || response.body, path, extension, gzip) end end diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index bd515bba82..50d7aac300 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -56,7 +56,7 @@ module ActionController include AbstractController::Helpers included do - config_accessor :helpers_path, :include_all_helpers + class_attribute :helpers_path, :include_all_helpers self.helpers_path ||= [] self.include_all_helpers = true end diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 264806cd36..56335a22cb 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -192,12 +192,15 @@ module ActionController return false unless password method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD'] - uri = credentials[:uri][0,1] == '/' ? request.fullpath : request.url + uri = credentials[:uri][0,1] == '/' ? request.original_fullpath : request.original_url - [true, false].any? do |password_is_ha1| - expected = expected_response(method, uri, credentials, password, password_is_ha1) - expected == credentials[:response] - end + [true, false].any? do |trailing_question_mark| + [true, false].any? do |password_is_ha1| + _uri = trailing_question_mark ? uri + "?" : uri + expected = expected_response(method, _uri, credentials, password, password_is_ha1) + expected == credentials[:response] + end + end end end diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb index c252e01cf5..8ac8d34430 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb @@ -23,7 +23,7 @@ module HTML #:nodoc: # Create a new Tokenizer for the given text. def initialize(text) - text.encode! if text.encoding_aware? + text.encode! @scanner = StringScanner.new(text) @position = 0 @line = 0 diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 4b821037dc..070ffbdceb 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -29,6 +29,7 @@ $:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include require 'active_support' require 'active_support/dependencies/autoload' +require 'active_support/core_ext/module/attribute_accessors' require 'action_pack' require 'active_model' @@ -59,7 +60,6 @@ module ActionDispatch autoload :PublicExceptions autoload :Reloader autoload :RemoteIp - autoload :Rescue autoload :ShowExceptions autoload :Static end @@ -88,6 +88,8 @@ module ActionDispatch autoload :CacheStore, 'action_dispatch/middleware/session/cache_store' end + mattr_accessor :test_app + autoload_under 'testing' do autoload :Assertions autoload :Integration diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index ef5d207b26..d9b63faf5e 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -41,8 +41,6 @@ module ActionDispatch # you'll get a weird error down the road, but our form handling # should really prevent that from happening def encode_params(params) - return params unless "ruby".encoding_aware? - if params.is_a?(String) return params.force_encoding("UTF-8").encode! elsif !params.is_a?(Hash) diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index c5c48ec489..0a0ebe7fad 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -122,10 +122,18 @@ module ActionDispatch Http::Headers.new(@env) end + def original_fullpath + @original_fullpath ||= (env["ORIGINAL_FULLPATH"] || fullpath) + end + def fullpath @fullpath ||= super end + def original_url + base_url + original_fullpath + end + def media_type content_mime_type.to_s end @@ -181,7 +189,7 @@ module ActionDispatch # variable is already set, wrap it in a StringIO. def body if raw_post = @env['RAW_POST_DATA'] - raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding) + raw_post.force_encoding(Encoding::BINARY) StringIO.new(raw_post) else @env['rack.input'] diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 5797c63924..84732085f0 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -121,14 +121,7 @@ module ActionDispatch # :nodoc: def body=(body) @blank = true if body == EMPTY - # Explicitly check for strings. This is *wrong* theoretically - # but if we don't check this, the performance on string bodies - # is bad on Ruby 1.8 (because strings responds to each then). - @body = if body.respond_to?(:to_str) || !body.respond_to?(:each) - [body] - else - body - end + @body = body.respond_to?(:each) ? body : [body] end def body_parts diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index 94fa747a79..5ab99d1061 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -22,8 +22,8 @@ module ActionDispatch private def encode_filename(filename) - # Encode the filename in the utf8 encoding, unless it is nil or we're in 1.8 - if "ruby".encoding_aware? && filename + # Encode the filename in the utf8 encoding, unless it is nil + if filename filename.force_encoding("UTF-8").encode! else filename diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb index dabbabb1e6..b903f98761 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -76,7 +76,7 @@ module ActionDispatch end def stderr_logger - @stderr_logger ||= Logger.new($stderr) + @stderr_logger ||= ActiveSupport::Logger.new($stderr) end end end diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb index 6ded9dbfed..1cb803ffb9 100644 --- a/actionpack/lib/action_dispatch/middleware/params_parser.rb +++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb @@ -69,7 +69,7 @@ module ActionDispatch end def logger(env) - env['action_dispatch.logger'] || Logger.new($stderr) + env['action_dispatch.logger'] || ActiveSupport::Logger.new($stderr) end end end diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb index bee446c8a5..d5a0b80fd5 100644 --- a/actionpack/lib/action_dispatch/middleware/request_id.rb +++ b/actionpack/lib/action_dispatch/middleware/request_id.rb @@ -33,7 +33,7 @@ module ActionDispatch end def internal_request_id - SecureRandom.hex(16) + SecureRandom.uuid end end end diff --git a/actionpack/lib/action_dispatch/middleware/rescue.rb b/actionpack/lib/action_dispatch/middleware/rescue.rb deleted file mode 100644 index aee672112c..0000000000 --- a/actionpack/lib/action_dispatch/middleware/rescue.rb +++ /dev/null @@ -1,26 +0,0 @@ -module ActionDispatch - class Rescue - def initialize(app, rescuers = {}, &block) - @app, @rescuers = app, {} - rescuers.each { |exception, rescuer| rescue_from(exception, rescuer) } - instance_eval(&block) if block_given? - end - - def call(env) - @app.call(env) - rescue Exception => exception - if rescuer = @rescuers[exception.class.name] - env['action_dispatch.rescue.exception'] = exception - rescuer.call(env) - else - raise exception - end - end - - protected - def rescue_from(exception, rescuer) - exception = exception.class.name if exception.is_a?(Exception) - @rescuers[exception.to_s] = rescuer - end - end -end diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index f75b4d4d04..6a8e690d18 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -30,7 +30,7 @@ module ActionDispatch def generate_sid sid = SecureRandom.hex(16) - sid.encode!('UTF-8') if sid.respond_to?(:encode!) + sid.encode!('UTF-8') sid end diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index a4f4825f92..46c06386d8 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -28,6 +28,8 @@ module ActionDispatch config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil? ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie + + ActionDispatch.test_app = app end end end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 4d83c6dee1..2117cb76b5 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1432,8 +1432,7 @@ module ActionDispatch end def action_path(name, path = nil) #:nodoc: - # Ruby 1.8 can't transform empty strings to symbols - name = name.to_sym if name.is_a?(String) && !name.empty? + name = name.to_sym if name.is_a?(String) path || @scope[:path_names][name] || name.to_s end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 6d6de36a08..d065d9f9d8 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -584,7 +584,7 @@ module ActionDispatch @router.recognize(req) do |route, matches, params| params.each do |key, value| if value.is_a?(String) - value = value.dup.force_encoding(Encoding::BINARY) if value.encoding_aware? + value = value.dup.force_encoding(Encoding::BINARY) params[key] = URI.parser.unescape(value) end end diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 0f1bb9f260..26db8662a8 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -463,9 +463,12 @@ module ActionDispatch @@app = nil def self.app - # DEPRECATE Rails application fallback - # This should be set by the initializer - @@app || (defined?(Rails.application) && Rails.application) || nil + if !@@app && !ActionDispatch.test_app + ActiveSupport::Deprecation.warn "Rails application fallback is deprecated " \ + "and no longer works, please set ActionDispatch.test_app", caller + end + + @@app || ActionDispatch.test_app end def self.app=(app) diff --git a/actionpack/lib/action_view/buffers.rb b/actionpack/lib/action_view/buffers.rb index be7f65c2ce..2372d3c433 100644 --- a/actionpack/lib/action_view/buffers.rb +++ b/actionpack/lib/action_view/buffers.rb @@ -4,7 +4,7 @@ module ActionView class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc: def initialize(*) super - encode! if encoding_aware? + encode! end def <<(value) diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index 8abd85c3a3..0a0d31dded 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -181,7 +181,7 @@ module ActionView def with_output_buffer(buf = nil) #:nodoc: unless buf buf = ActionView::OutputBuffer.new - buf.force_encoding(output_buffer.encoding) if output_buffer.respond_to?(:encoding) && buf.respond_to?(:force_encoding) + buf.force_encoding(output_buffer.encoding) if output_buffer end self.output_buffer, old_buffer = buf, output_buffer yield diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index 842f4c23a3..309923490c 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -14,11 +14,7 @@ module ActionView "'" => "\\'" } - if "ruby".encoding_aware? - JS_ESCAPE_MAP["\342\200\250".force_encoding('UTF-8').encode!] = '
' - else - JS_ESCAPE_MAP["\342\200\250"] = '
' - end + JS_ESCAPE_MAP["\342\200\250".force_encoding('UTF-8').encode!] = '
' # Escapes carriage returns and single and double quotes for JavaScript segments. # diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb index 3e3a44b432..06148ccc98 100644 --- a/actionpack/lib/action_view/renderer/template_renderer.rb +++ b/actionpack/lib/action_view/renderer/template_renderer.rb @@ -25,6 +25,8 @@ module ActionView elsif options.key?(:template) options[:template].respond_to?(:render) ? options[:template] : find_template(options[:template], options[:prefixes], false, keys, @details) + else + raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file or :text option." end end diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index eac6287b0b..2d9fc3df7a 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -184,7 +184,7 @@ module ActionView # before passing the source on to the template engine, leaving a # blank line in its stead. def encode! - return unless source.encoding_aware? && source.encoding == Encoding::BINARY + return unless source.encoding == Encoding::BINARY # Look for # encoding: *. If we find one, we'll encode the # String in that encoding, otherwise, we'll use the @@ -265,20 +265,18 @@ module ActionView end end_src - if source.encoding_aware? - # Make sure the source is in the encoding of the returned code - source.force_encoding(code.encoding) + # Make sure the source is in the encoding of the returned code + source.force_encoding(code.encoding) - # In case we get back a String from a handler that is not in - # BINARY or the default_internal, encode it to the default_internal - source.encode! + # In case we get back a String from a handler that is not in + # BINARY or the default_internal, encode it to the default_internal + source.encode! - # Now, validate that the source we got back from the template - # handler is valid in the default_internal. This is for handlers - # that handle encoding but screw up - unless source.valid_encoding? - raise WrongEncodingError.new(@source, Encoding.default_internal) - end + # Now, validate that the source we got back from the template + # handler is valid in the default_internal. This is for handlers + # that handle encoding but screw up + unless source.valid_encoding? + raise WrongEncodingError.new(@source, Encoding.default_internal) end begin diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb index 25f26dd609..323df67c97 100644 --- a/actionpack/lib/action_view/template/handlers/erb.rb +++ b/actionpack/lib/action_view/template/handlers/erb.rb @@ -67,23 +67,19 @@ module ActionView end def call(template) - if template.source.encoding_aware? - # First, convert to BINARY, so in case the encoding is - # wrong, we can still find an encoding tag - # (<%# encoding %>) inside the String using a regular - # expression - template_source = template.source.dup.force_encoding("BINARY") + # First, convert to BINARY, so in case the encoding is + # wrong, we can still find an encoding tag + # (<%# encoding %>) inside the String using a regular + # expression + template_source = template.source.dup.force_encoding("BINARY") - erb = template_source.gsub(ENCODING_TAG, '') - encoding = $2 + erb = template_source.gsub(ENCODING_TAG, '') + encoding = $2 - erb.force_encoding valid_encoding(template.source.dup, encoding) + erb.force_encoding valid_encoding(template.source.dup, encoding) - # Always make sure we return a String in the default_internal - erb.encode! - else - erb = template.source.dup - end + # Always make sure we return a String in the default_internal + erb.encode! self.class.erb_implementation.new( erb, diff --git a/actionpack/lib/sprockets/helpers/rails_helper.rb b/actionpack/lib/sprockets/helpers/rails_helper.rb index 1bd9d5ca26..c34b3f6f26 100644 --- a/actionpack/lib/sprockets/helpers/rails_helper.rb +++ b/actionpack/lib/sprockets/helpers/rails_helper.rb @@ -114,11 +114,6 @@ module Sprockets class AssetNotPrecompiledError < StandardError; end - # Return the filesystem path for the source - def compute_source_path(source, ext) - asset_for(source, ext) - end - def asset_for(source, ext) source = source.to_s return nil if is_uri?(source) diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 40ca23a39d..63109d592a 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -14,14 +14,11 @@ ENV['TMPDIR'] = File.join(File.dirname(__FILE__), 'tmp') require 'active_support/core_ext/kernel/reporting' -require 'active_support/core_ext/string/encoding' -if "ruby".encoding_aware? - # These are the normal settings that will be set up by Railties - # TODO: Have these tests support other combinations of these values - silence_warnings do - Encoding.default_internal = "UTF-8" - Encoding.default_external = "UTF-8" - end +# These are the normal settings that will be set up by Railties +# TODO: Have these tests support other combinations of these values +silence_warnings do + Encoding.default_internal = "UTF-8" + Encoding.default_external = "UTF-8" end require 'test/unit' diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index 015e6b9955..34a38a5567 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -14,10 +14,14 @@ class CachingController < ActionController::Base end class PageCachingTestController < CachingController + self.page_cache_compression = :best_compression + caches_page :ok, :no_content, :if => Proc.new { |c| !c.request.format.json? } caches_page :found, :not_found caches_page :about_me - + caches_page :default_gzip + caches_page :no_gzip, :gzip => false + caches_page :gzip_level, :gzip => :best_speed def ok head :ok @@ -40,6 +44,18 @@ class PageCachingTestController < CachingController cache_page("Super soaker", "/index.html") end + def default_gzip + render :text => "Text" + end + + def no_gzip + render :text => "PNG" + end + + def gzip_level + render :text => "Big text" + end + def expire_custom_path expire_page("/index.html") head :ok @@ -115,6 +131,30 @@ class PageCachingTest < ActionController::TestCase assert !File.exist?("#{FILE_STORE_PATH}/index.html") end + def test_should_gzip_cache + get :custom_path + assert File.exist?("#{FILE_STORE_PATH}/index.html.gz") + + get :expire_custom_path + assert !File.exist?("#{FILE_STORE_PATH}/index.html.gz") + end + + def test_should_allow_to_disable_gzip + get :no_gzip + assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/no_gzip.html") + assert !File.exist?("#{FILE_STORE_PATH}/page_caching_test/no_gzip.html.gz") + end + + def test_should_use_config_gzip_by_default + @controller.expects(:cache_page).with(nil, nil, Zlib::BEST_COMPRESSION) + get :default_gzip + end + + def test_should_set_gzip_level + @controller.expects(:cache_page).with(nil, nil, Zlib::BEST_SPEED) + get :gzip_level + end + def test_should_cache_without_trailing_slash_on_url @controller.class.cache_page 'cached content', '/page_caching_test/trailing_slash' assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/trailing_slash.html") @@ -194,7 +234,7 @@ class ActionCachingTestController < CachingController caches_action :show, :cache_path => 'http://test.host/custom/show' caches_action :edit, :cache_path => Proc.new { |c| c.params[:id] ? "http://test.host/#{c.params[:id]};edit" : "http://test.host/edit" } caches_action :with_layout - caches_action :with_format_and_http_param, :cache_path => Proc.new { |c| { :key => 'value' } } + caches_action :with_format_and_http_param, :cache_path => Proc.new { |c| { :key => 'value' } } caches_action :layout_false, :layout => false caches_action :record_not_found, :four_oh_four, :simple_runtime_error @@ -224,7 +264,7 @@ class ActionCachingTestController < CachingController @cache_this = MockTime.now.to_f.to_s render :text => @cache_this end - + def record_not_found raise ActiveRecord::RecordNotFound, "oops!" end diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb index b011536717..a91e3cafa5 100644 --- a/actionpack/test/controller/http_digest_authentication_test.rb +++ b/actionpack/test/controller/http_digest_authentication_test.rb @@ -139,7 +139,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase test "authentication request with request-uri that doesn't match credentials digest-uri" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') - @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest/altered/uri" + @request.env['ORIGINAL_FULLPATH'] = "/http_digest_authentication_test/dummy_digest/altered/uri" get :display assert_response :unauthorized @@ -208,6 +208,44 @@ class HttpDigestAuthenticationTest < ActionController::TestCase assert !ActionController::HttpAuthentication::Digest.validate_digest_response(@request, "SuperSecret"){nil} end + test "authentication request with request-uri ending in '/'" do + @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest/" + @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') + + # simulate normalizing PATH_INFO + @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest" + get :display + + assert_response :success + assert_equal 'Definitely Maybe', @response.body + end + + test "authentication request with request-uri ending in '?'" do + @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest/?" + @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') + + # simulate normalizing PATH_INFO + @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest" + get :display + + assert_response :success + assert_equal 'Definitely Maybe', @response.body + end + + test "authentication request with absolute uri in credentials (as in IE) ending with /" do + @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest/" + @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:uri => "http://test.host/http_digest_authentication_test/dummy_digest/", + :username => 'pretty', :password => 'please') + + # simulate normalizing PATH_INFO + @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest" + get :display + + assert_response :success + assert assigns(:logged_in) + assert_equal 'Definitely Maybe', @response.body + end + private def encode_credentials(options) @@ -228,7 +266,10 @@ class HttpDigestAuthenticationTest < ActionController::TestCase credentials = decode_credentials(@response.headers['WWW-Authenticate']) credentials.merge!(options) - credentials.merge!(:uri => @request.env['PATH_INFO'].to_s) + path_info = @request.env['PATH_INFO'].to_s + uri = options[:uri] || path_info + credentials.merge!(:uri => uri) + @request.env["ORIGINAL_FULLPATH"] = path_info ActionController::HttpAuthentication::Digest.encode_credentials(method, credentials, password, options[:password_is_ha1]) end diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb index c445285538..86d6737cbb 100644 --- a/actionpack/test/controller/rescue_test.rb +++ b/actionpack/test/controller/rescue_test.rb @@ -338,23 +338,8 @@ class RescueTest < ActionDispatch::IntegrationTest end end - test 'rescue routing exceptions' do - raiser = proc { |env| raise ActionController::RoutingError, "Did not handle the request" } - @app = ActionDispatch::Rescue.new(raiser) do - rescue_from ActionController::RoutingError, lambda { |env| [200, {"Content-Type" => "text/html"}, ["Gotcha!"]] } - end - - get '/b00m' - assert_equal "Gotcha!", response.body - end - - test 'unrescued exception' do - raiser = proc { |env| raise ActionController::RoutingError, "Did not handle the request" } - @app = ActionDispatch::Rescue.new(raiser) - assert_raise(ActionController::RoutingError) { get '/b00m' } - end - private + def with_test_routing with_routing do |set| set.draw do diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index f40d663ae8..c9785d9b8a 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -78,11 +78,32 @@ class LegacyRouteSetTests < Test::Unit::TestCase attr_reader :rs def setup - @rs = ::ActionDispatch::Routing::RouteSet.new + @rs = ::ActionDispatch::Routing::RouteSet.new + @response = nil end - def teardown - @rs.clear! + def get(uri_or_host, path = nil, port = nil) + host = uri_or_host.host unless path + path ||= uri_or_host.path + + params = {'PATH_INFO' => path, + 'REQUEST_METHOD' => 'GET', + 'HTTP_HOST' => host} + + @rs.call(params)[2].join + end + + def test_regexp_precidence + @rs.draw do + match '/whois/:domain', :constraints => { + :domain => /\w+\.[\w\.]+/ }, + :to => lambda { |env| [200, {}, %w{regexp}] } + + match '/whois/:id', :to => lambda { |env| [200, {}, %w{id}] } + end + + assert_equal 'regexp', get(URI('http://example.org/whois/example.org')) + assert_equal 'id', get(URI('http://example.org/whois/123')) end def test_class_and_lambda_constraints @@ -94,46 +115,50 @@ class LegacyRouteSetTests < Test::Unit::TestCase @rs.draw do match '/', :constraints => subdomain.new, - :to => lambda { |env| [200, {}, 'default'] } + :to => lambda { |env| [200, {}, %w{default}] } match '/', :constraints => { :subdomain => 'clients' }, - :to => lambda { |env| [200, {}, 'clients'] } + :to => lambda { |env| [200, {}, %w{clients}] } end - body = @rs.call({'PATH_INFO' => '/', - 'REQUEST_METHOD' => 'GET', - 'HTTP_HOST' => 'www.example.org'})[2] - - assert_equal 'default', body - - body = @rs.call({'PATH_INFO' => '/', - 'REQUEST_METHOD' => 'GET', - 'HTTP_HOST' => 'clients.example.org'})[2] - - assert_equal 'clients', body + assert_equal 'default', get(URI('http://www.example.org/')) + assert_equal 'clients', get(URI('http://clients.example.org/')) end def test_lambda_constraints @rs.draw do match '/', :constraints => lambda { |req| req.subdomain.present? and req.subdomain != "clients" }, - :to => lambda { |env| [200, {}, 'default'] } + :to => lambda { |env| [200, {}, %w{default}] } match '/', :constraints => lambda { |req| req.subdomain.present? && req.subdomain == "clients" }, - :to => lambda { |env| [200, {}, 'clients'] } + :to => lambda { |env| [200, {}, %w{clients}] } end - body = @rs.call({'PATH_INFO' => '/', - 'REQUEST_METHOD' => 'GET', - 'HTTP_HOST' => 'www.example.org'})[2] - - assert_equal 'default', body + assert_equal 'default', get(URI('http://www.example.org/')) + assert_equal 'clients', get(URI('http://clients.example.org/')) + end - body = @rs.call({'PATH_INFO' => '/', - 'REQUEST_METHOD' => 'GET', - 'HTTP_HOST' => 'clients.example.org'})[2] + def test_empty_string_match + rs.draw do + get '/:username', :constraints => { :username => /[^\/]+/ }, + :to => lambda { |e| [200, {}, ['foo']] } + end + assert_equal 'Not Found', get(URI('http://example.org/')) + assert_equal 'foo', get(URI('http://example.org/hello')) + end - assert_equal 'clients', body + def test_non_greedy_glob_regexp + params = nil + rs.draw do + get '/posts/:id(/*filters)', :constraints => { :filters => /.+?/ }, + :to => lambda { |e| + params = e["action_dispatch.request.path_parameters"] + [200, {}, ['foo']] + } + end + assert_equal 'foo', get(URI('http://example.org/posts/1/foo.js')) + assert_equal({:id=>"1", :filters=>"foo", :format=>"js"}, params) end def test_draw_with_block_arity_one_raises @@ -458,7 +483,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase assert_equal({ :controller => "content", :action => 'show_page', :id => 'foo' }, rs.recognize_path("/page/foo")) token = "\321\202\320\265\320\272\321\201\321\202" # 'text' in Russian - token.force_encoding(Encoding::BINARY) if token.respond_to?(:force_encoding) + token.force_encoding(Encoding::BINARY) escaped_token = CGI::escape(token) assert_equal '/page/' + escaped_token, url_for(rs, { :controller => 'content', :action => 'show_page', :id => token }) diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb index 8f885ff28e..36884846be 100644 --- a/actionpack/test/controller/send_file_test.rb +++ b/actionpack/test/controller/send_file_test.rb @@ -61,7 +61,7 @@ class SendFileTest < ActionController::TestCase require 'stringio' output = StringIO.new output.binmode - output.string.force_encoding(file_data.encoding) if output.string.respond_to?(:force_encoding) + output.string.force_encoding(file_data.encoding) assert_nothing_raised { response.body_parts.each { |part| output << part.to_s } } assert_equal file_data, output.string end diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb index 0e75a23cfd..b3b7ece518 100644 --- a/actionpack/test/controller/test_test.rb +++ b/actionpack/test/controller/test_test.rb @@ -673,7 +673,7 @@ XML path = "#{FILES_DIR}/#{filename}" content_type = 'image/png' expected = File.read(path) - expected.force_encoding(Encoding::BINARY) if expected.respond_to?(:force_encoding) + expected.force_encoding(Encoding::BINARY) file = Rack::Test::UploadedFile.new(path, content_type) assert_equal filename, file.original_filename diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb index 560ea00923..d144f013f5 100644 --- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb @@ -89,7 +89,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest # Rack doesn't handle multipart/mixed for us. files = params['files'] - files.force_encoding('ASCII-8BIT') if files.respond_to?(:force_encoding) + files.force_encoding('ASCII-8BIT') assert_equal 19756, files.size end diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb index 04a0fb6f34..05569561d2 100644 --- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb @@ -146,8 +146,6 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest end def assert_utf8(object) - return unless "ruby".encoding_aware? - correct_encoding = Encoding.default_internal unless object.is_a?(Hash) diff --git a/actionpack/test/dispatch/request_id_test.rb b/actionpack/test/dispatch/request_id_test.rb index b6e8b6c3ad..4b98cd32f2 100644 --- a/actionpack/test/dispatch/request_id_test.rb +++ b/actionpack/test/dispatch/request_id_test.rb @@ -14,7 +14,7 @@ class RequestIdTest < ActiveSupport::TestCase end test "generating a request id when none is supplied" do - assert_match(/\w+/, stub_request.uuid) + assert_match(/\w+-\w+-\w+-\w+-\w+/, stub_request.uuid) end private diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 4d805464c2..5b3d38c48c 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -618,6 +618,30 @@ class RequestTest < ActiveSupport::TestCase assert_equal "/authenticate?secret", path end + test "original_fullpath returns ORIGINAL_FULLPATH" do + request = stub_request('ORIGINAL_FULLPATH' => "/foo?bar") + + path = request.original_fullpath + assert_equal "/foo?bar", path + end + + test "original_url returns url built using ORIGINAL_FULLPATH" do + request = stub_request('ORIGINAL_FULLPATH' => "/foo?bar", + 'HTTP_HOST' => "example.org", + 'rack.url_scheme' => "http") + + url = request.original_url + assert_equal "http://example.org/foo?bar", url + end + + test "original_fullpath returns fullpath if ORIGINAL_FULLPATH is not present" do + request = stub_request('PATH_INFO' => "/foo", + 'QUERY_STRING' => "bar") + + path = request.original_fullpath + assert_equal "/foo?bar", path + end + protected def stub_request(env = {}) diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 9bdd5ecbc6..82d1200f8e 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -6,9 +6,6 @@ class ResponseTest < ActiveSupport::TestCase end def test_response_body_encoding - # FIXME: remove this conditional on Rails 4.0 - return unless "<3".encoding_aware? - body = ["hello".encode('utf-8')] response = ActionDispatch::Response.new 200, {}, body assert_equal Encoding::UTF_8, response.body.encoding diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 5325f81364..d5c1586600 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -2414,7 +2414,8 @@ class TestAppendingRoutes < ActionDispatch::IntegrationTest lambda { |e| [ 200, { 'Content-Type' => 'text/plain' }, [resp] ] } end - setup do + def setup + super s = self @app = ActionDispatch::Routing::RouteSet.new @app.append do diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb index 7e4a1519fb..0b95291e18 100644 --- a/actionpack/test/dispatch/uploaded_file_test.rb +++ b/actionpack/test/dispatch/uploaded_file_test.rb @@ -13,11 +13,9 @@ module ActionDispatch assert_equal 'foo', uf.original_filename end - if "ruby".encoding_aware? - def test_filename_should_be_in_utf_8 - uf = Http::UploadedFile.new(:filename => 'foo', :tempfile => Object.new) - assert_equal "UTF-8", uf.original_filename.encoding.to_s - end + def test_filename_should_be_in_utf_8 + uf = Http::UploadedFile.new(:filename => 'foo', :tempfile => Object.new) + assert_equal "UTF-8", uf.original_filename.encoding.to_s end def test_content_type diff --git a/actionpack/test/lib/testing_sandbox.rb b/actionpack/test/lib/testing_sandbox.rb deleted file mode 100644 index c36585104f..0000000000 --- a/actionpack/test/lib/testing_sandbox.rb +++ /dev/null @@ -1,15 +0,0 @@ -module TestingSandbox - # Temporarily replaces KCODE for the block - def with_kcode(kcode) - if RUBY_VERSION < '1.9' - old_kcode, $KCODE = $KCODE, kcode - begin - yield - ensure - $KCODE = old_kcode - end - else - yield - end - end -end diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb index 4b9c3c97b1..d98ffe8fa7 100644 --- a/actionpack/test/template/javascript_helper_test.rb +++ b/actionpack/test/template/javascript_helper_test.rb @@ -28,11 +28,7 @@ class JavaScriptHelperTest < ActionView::TestCase assert_equal %(This \\"thing\\" is really\\n netos\\'), escape_javascript(%(This "thing" is really\n netos')) assert_equal %(backslash\\\\test), escape_javascript( %(backslash\\test) ) assert_equal %(dont <\\/close> tags), escape_javascript(%(dont </close> tags)) - if "ruby".encoding_aware? - assert_equal %(unicode 
 newline), escape_javascript(%(unicode \342\200\250 newline).force_encoding('UTF-8').encode!) - else - assert_equal %(unicode 
 newline), escape_javascript(%(unicode \342\200\250 newline)) - end + assert_equal %(unicode 
 newline), escape_javascript(%(unicode \342\200\250 newline).force_encoding('UTF-8').encode!) assert_equal %(dont <\\/close> tags), j(%(dont </close> tags)) end diff --git a/actionpack/test/template/output_buffer_test.rb b/actionpack/test/template/output_buffer_test.rb index bd49a11af1..eb0df3d1ab 100644 --- a/actionpack/test/template/output_buffer_test.rb +++ b/actionpack/test/template/output_buffer_test.rb @@ -39,15 +39,13 @@ class OutputBufferTest < ActionController::TestCase assert_equal ['foo', 'bar'], body_parts end - if '1.9'.respond_to?(:force_encoding) - test 'flushing preserves output buffer encoding' do - original_buffer = ' '.force_encoding(Encoding::EUC_JP) - @vc.output_buffer = original_buffer - @vc.flush_output_buffer - assert_equal ['foo', original_buffer], body_parts - assert_not_equal original_buffer, output_buffer - assert_equal Encoding::EUC_JP, output_buffer.encoding - end + test 'flushing preserves output buffer encoding' do + original_buffer = ' '.force_encoding(Encoding::EUC_JP) + @vc.output_buffer = original_buffer + @vc.flush_output_buffer + assert_equal ['foo', original_buffer], body_parts + assert_not_equal original_buffer, output_buffer + assert_equal Encoding::EUC_JP, output_buffer.encoding end protected diff --git a/actionpack/test/template/output_safety_helper_test.rb b/actionpack/test/template/output_safety_helper_test.rb index fc127c24e9..76c71c9e6d 100644 --- a/actionpack/test/template/output_safety_helper_test.rb +++ b/actionpack/test/template/output_safety_helper_test.rb @@ -1,9 +1,7 @@ require 'abstract_unit' -require 'testing_sandbox' class OutputSafetyHelperTest < ActionView::TestCase tests ActionView::Helpers::OutputSafetyHelper - include TestingSandbox def setup @string = "hello" diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index 2ba86306f4..5d3dc73ed2 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -20,6 +20,13 @@ module RenderTestCases assert_equal ORIGINAL_LOCALES, I18n.available_locales.map {|l| l.to_s }.sort end + def test_render_without_options + @view.render() + flunk "Render did not raise ArgumentError" + rescue ArgumentError => e + assert_match "You invoked render but did not give any of :partial, :template, :inline, :file or :text option.", e.message + end + def test_render_file assert_equal "Hello world!", @view.render(:file => "test/hello_world") end @@ -43,21 +50,21 @@ module RenderTestCases assert_match "<h1>No Comment</h1>", @view.render(:template => "comments/empty", :formats => [:html]) assert_match "<error>No Comment</error>", @view.render(:template => "comments/empty", :formats => [:xml]) end - + def test_render_file_with_locale assert_equal "<h1>Kein Kommentar</h1>", @view.render(:file => "comments/empty", :locale => [:de]) assert_equal "<h1>Kein Kommentar</h1>", @view.render(:file => "comments/empty", :locale => :de) end - + def test_render_template_with_locale assert_equal "<h1>Kein Kommentar</h1>", @view.render(:template => "comments/empty", :locale => [:de]) end - + def test_render_file_with_handlers assert_equal "<h1>No Comment</h1>\n", @view.render(:file => "comments/empty", :handlers => [:builder]) assert_equal "<h1>No Comment</h1>\n", @view.render(:file => "comments/empty", :handlers => :builder) end - + def test_render_template_with_handlers assert_equal "<h1>No Comment</h1>\n", @view.render(:template => "comments/empty", :handlers => [:builder]) end @@ -403,51 +410,49 @@ class LazyViewRenderTest < ActiveSupport::TestCase GC.start end - if '1.9'.respond_to?(:force_encoding) - def test_render_utf8_template_with_magic_comment - with_external_encoding Encoding::ASCII_8BIT do - result = @view.render(:file => "test/utf8_magic", :formats => [:html], :layouts => "layouts/yield") - assert_equal Encoding::UTF_8, result.encoding - assert_equal "\nРусский \nтекст\n\nUTF-8\nUTF-8\nUTF-8\n", result - end + def test_render_utf8_template_with_magic_comment + with_external_encoding Encoding::ASCII_8BIT do + result = @view.render(:file => "test/utf8_magic", :formats => [:html], :layouts => "layouts/yield") + assert_equal Encoding::UTF_8, result.encoding + assert_equal "\nРусский \nтекст\n\nUTF-8\nUTF-8\nUTF-8\n", result end + end - def test_render_utf8_template_with_default_external_encoding - with_external_encoding Encoding::UTF_8 do - result = @view.render(:file => "test/utf8", :formats => [:html], :layouts => "layouts/yield") - assert_equal Encoding::UTF_8, result.encoding - assert_equal "Русский текст\n\nUTF-8\nUTF-8\nUTF-8\n", result - end + def test_render_utf8_template_with_default_external_encoding + with_external_encoding Encoding::UTF_8 do + result = @view.render(:file => "test/utf8", :formats => [:html], :layouts => "layouts/yield") + assert_equal Encoding::UTF_8, result.encoding + assert_equal "Русский текст\n\nUTF-8\nUTF-8\nUTF-8\n", result end + end - def test_render_utf8_template_with_incompatible_external_encoding - with_external_encoding Encoding::SHIFT_JIS do - begin - @view.render(:file => "test/utf8", :formats => [:html], :layouts => "layouts/yield") - flunk 'Should have raised incompatible encoding error' - rescue ActionView::Template::Error => error - assert_match 'Your template was not saved as valid Shift_JIS', error.original_exception.message - end + def test_render_utf8_template_with_incompatible_external_encoding + with_external_encoding Encoding::SHIFT_JIS do + begin + @view.render(:file => "test/utf8", :formats => [:html], :layouts => "layouts/yield") + flunk 'Should have raised incompatible encoding error' + rescue ActionView::Template::Error => error + assert_match 'Your template was not saved as valid Shift_JIS', error.original_exception.message end end + end - def test_render_utf8_template_with_partial_with_incompatible_encoding - with_external_encoding Encoding::SHIFT_JIS do - begin - @view.render(:file => "test/utf8_magic_with_bare_partial", :formats => [:html], :layouts => "layouts/yield") - flunk 'Should have raised incompatible encoding error' - rescue ActionView::Template::Error => error - assert_match 'Your template was not saved as valid Shift_JIS', error.original_exception.message - end + def test_render_utf8_template_with_partial_with_incompatible_encoding + with_external_encoding Encoding::SHIFT_JIS do + begin + @view.render(:file => "test/utf8_magic_with_bare_partial", :formats => [:html], :layouts => "layouts/yield") + flunk 'Should have raised incompatible encoding error' + rescue ActionView::Template::Error => error + assert_match 'Your template was not saved as valid Shift_JIS', error.original_exception.message end end + end - def with_external_encoding(encoding) - old = Encoding.default_external - silence_warnings { Encoding.default_external = encoding } - yield - ensure - silence_warnings { Encoding.default_external = old } - end + def with_external_encoding(encoding) + old = Encoding.default_external + silence_warnings { Encoding.default_external = encoding } + yield + ensure + silence_warnings { Encoding.default_external = old } end end diff --git a/actionpack/test/template/sanitize_helper_test.rb b/actionpack/test/template/sanitize_helper_test.rb index 222d4dbf4c..4182af590e 100644 --- a/actionpack/test/template/sanitize_helper_test.rb +++ b/actionpack/test/template/sanitize_helper_test.rb @@ -1,11 +1,9 @@ require 'abstract_unit' -require 'testing_sandbox' # The exhaustive tests are in test/controller/html/sanitizer_test.rb. # This tests the that the helpers hook up correctly to the sanitizer classes. class SanitizeHelperTest < ActionView::TestCase tests ActionView::Helpers::SanitizeHelper - include TestingSandbox def test_strip_links assert_equal "Dont touch me", strip_links("Dont touch me") diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb index 13d30a93ce..f9c228f0c3 100644 --- a/actionpack/test/template/template_test.rb +++ b/actionpack/test/template/template_test.rb @@ -114,67 +114,65 @@ class TestERBTemplate < ActiveSupport::TestCase end end - if "ruby".encoding_aware? - def test_resulting_string_is_utf8 - @template = new_template - assert_equal Encoding::UTF_8, render.encoding - end + def test_resulting_string_is_utf8 + @template = new_template + assert_equal Encoding::UTF_8, render.encoding + end + + def test_no_magic_comment_word_with_utf_8 + @template = new_template("hello \u{fc}mlat") + assert_equal Encoding::UTF_8, render.encoding + assert_equal "hello \u{fc}mlat", render + end - def test_no_magic_comment_word_with_utf_8 - @template = new_template("hello \u{fc}mlat") + # This test ensures that if the default_external + # is set to something other than UTF-8, we don't + # get any errors and get back a UTF-8 String. + def test_default_external_works + with_external_encoding "ISO-8859-1" do + @template = new_template("hello \xFCmlat") assert_equal Encoding::UTF_8, render.encoding assert_equal "hello \u{fc}mlat", render end + end - # This test ensures that if the default_external - # is set to something other than UTF-8, we don't - # get any errors and get back a UTF-8 String. - def test_default_external_works - with_external_encoding "ISO-8859-1" do - @template = new_template("hello \xFCmlat") - assert_equal Encoding::UTF_8, render.encoding - assert_equal "hello \u{fc}mlat", render - end - end - - def test_encoding_can_be_specified_with_magic_comment - @template = new_template("# encoding: ISO-8859-1\nhello \xFCmlat") - assert_equal Encoding::UTF_8, render.encoding - assert_equal "\nhello \u{fc}mlat", render - end + def test_encoding_can_be_specified_with_magic_comment + @template = new_template("# encoding: ISO-8859-1\nhello \xFCmlat") + assert_equal Encoding::UTF_8, render.encoding + assert_equal "\nhello \u{fc}mlat", render + end - # TODO: This is currently handled inside ERB. The case of explicitly - # lying about encodings via the normal Rails API should be handled - # inside Rails. - def test_lying_with_magic_comment - assert_raises(ActionView::Template::Error) do - @template = new_template("# encoding: UTF-8\nhello \xFCmlat", :virtual_path => nil) - render - end + # TODO: This is currently handled inside ERB. The case of explicitly + # lying about encodings via the normal Rails API should be handled + # inside Rails. + def test_lying_with_magic_comment + assert_raises(ActionView::Template::Error) do + @template = new_template("# encoding: UTF-8\nhello \xFCmlat", :virtual_path => nil) + render end + end - def test_encoding_can_be_specified_with_magic_comment_in_erb - with_external_encoding Encoding::UTF_8 do - @template = new_template("<%# encoding: ISO-8859-1 %>hello \xFCmlat", :virtual_path => nil) - assert_equal Encoding::UTF_8, render.encoding - assert_equal "hello \u{fc}mlat", render - end + def test_encoding_can_be_specified_with_magic_comment_in_erb + with_external_encoding Encoding::UTF_8 do + @template = new_template("<%# encoding: ISO-8859-1 %>hello \xFCmlat", :virtual_path => nil) + assert_equal Encoding::UTF_8, render.encoding + assert_equal "hello \u{fc}mlat", render end + end - def test_error_when_template_isnt_valid_utf8 - assert_raises(ActionView::Template::Error, /\xFC/) do - @template = new_template("hello \xFCmlat", :virtual_path => nil) - render - end + def test_error_when_template_isnt_valid_utf8 + assert_raises(ActionView::Template::Error, /\xFC/) do + @template = new_template("hello \xFCmlat", :virtual_path => nil) + render end + end - def with_external_encoding(encoding) - old = Encoding.default_external - Encoding::Converter.new old, encoding if old != encoding - silence_warnings { Encoding.default_external = encoding } - yield - ensure - silence_warnings { Encoding.default_external = old } - end + def with_external_encoding(encoding) + old = Encoding.default_external + Encoding::Converter.new old, encoding if old != encoding + silence_warnings { Encoding.default_external = encoding } + yield + ensure + silence_warnings { Encoding.default_external = old } end end diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index 96af824b51..839bf900dc 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -1,10 +1,8 @@ # encoding: utf-8 require 'abstract_unit' -require 'testing_sandbox' class TextHelperTest < ActionView::TestCase tests ActionView::Helpers::TextHelper - include TestingSandbox def setup super diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc index 67701bc422..9208145507 100644 --- a/activemodel/README.rdoc +++ b/activemodel/README.rdoc @@ -197,7 +197,9 @@ Source code can be downloaded as part of the Rails project on GitHub == License -Active Model is released under the MIT license. +Active Model is released under the MIT license: + +* http://www.opensource.org/licenses/MIT == Support diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index dba059eacd..d7ec7e6ca5 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -66,47 +66,6 @@ module ActiveModel end module ClassMethods - def define_attr_method(name, value=nil, deprecation_warning = true, &block) #:nodoc: - # This deprecation_warning param is for internal use so that we can silence - # the warning from Active Record, because we are implementing more specific - # messages there instead. - # - # It doesn't apply to the original_#{name} method as we want to warn if - # people are calling that regardless. - if deprecation_warning - ActiveSupport::Deprecation.warn("define_attr_method is deprecated and will be removed without replacement.") - end - - sing = singleton_class - sing.class_eval <<-eorb, __FILE__, __LINE__ + 1 - remove_possible_method :'original_#{name}' - remove_possible_method :'_original_#{name}' - alias_method :'_original_#{name}', :'#{name}' - define_method :'original_#{name}' do - ActiveSupport::Deprecation.warn( - "This method is generated by ActiveModel::AttributeMethods::ClassMethods#define_attr_method, " \ - "which is deprecated and will be removed." - ) - send(:'_original_#{name}') - end - eorb - if block_given? - sing.send :define_method, name, &block - else - # If we can compile the method name, do it. Otherwise use define_method. - # This is an important *optimization*, please don't change it. define_method - # has slower dispatch and consumes more memory. - if name =~ NAME_COMPILABLE_REGEXP - sing.class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def #{name}; #{value.nil? ? 'nil' : value.to_s.inspect}; end - RUBY - else - value = value.to_s if value - sing.send(:define_method, name) { value } - end - end - end - # Declares a method available for all attributes with the given prefix. # Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method. # diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml index 44425b4a28..ba49c6beaa 100644 --- a/activemodel/lib/active_model/locale/en.yml +++ b/activemodel/lib/active_model/locale/en.yml @@ -23,5 +23,6 @@ en: equal_to: "must be equal to %{count}" less_than: "must be less than %{count}" less_than_or_equal_to: "must be less than or equal to %{count}" + other_than: "must be other than %{count}" odd: "must be odd" even: "must be even" diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb index 3f9feb7631..c895968f77 100644 --- a/activemodel/lib/active_model/mass_assignment_security.rb +++ b/activemodel/lib/active_model/mass_assignment_security.rb @@ -70,12 +70,13 @@ module ActiveModel # # class Customer # include ActiveModel::MassAssignmentSecurity - # - # attr_accessor :name, :credit_rating - # - # attr_protected :credit_rating, :last_login - # attr_protected :last_login, :as => :admin - # + # + # attr_accessor :name, :email, :logins_count + # + # attr_protected :logins_count + # # Suppose that admin can not change email for customer + # attr_protected :logins_count, :email, :as => :admin + # # def assign_attributes(values, options = {}) # sanitize_for_mass_assignment(values, options[:as]).each do |k, v| # send("#{k}=", v) @@ -86,21 +87,21 @@ module ActiveModel # When using the :default role : # # customer = Customer.new - # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default) + # customer.assign_attributes({ "name" => "David", "email" => "a@b.com", :logins_count => 5 }, :as => :default) # customer.name # => "David" - # customer.credit_rating # => nil - # customer.last_login # => nil - # - # customer.credit_rating = "Average" - # customer.credit_rating # => "Average" + # customer.email # => "a@b.com" + # customer.logins_count # => nil # # And using the :admin role : # # customer = Customer.new - # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin) + # customer.assign_attributes({ "name" => "David", "email" => "a@b.com", :logins_count => 5}, :as => :admin) # customer.name # => "David" - # customer.credit_rating # => "Excellent" - # customer.last_login # => nil + # customer.email # => nil + # customer.logins_count # => nil + # + # customer.email = "c@d.com" + # customer.email # => "c@d.com" # # To start from an all-closed default and enable attributes as needed, # have a look at +attr_accessible+. diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb index a38de27b3c..0eba241333 100644 --- a/activemodel/lib/active_model/validations/length.rb +++ b/activemodel/lib/active_model/validations/length.rb @@ -13,8 +13,7 @@ module ActiveModel def initialize(options) if range = (options.delete(:in) || options.delete(:within)) raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range) - options[:minimum], options[:maximum] = range.begin, range.end - options[:maximum] -= 1 if range.exclude_end? + options[:minimum], options[:maximum] = range.min, range.max end super @@ -57,12 +56,8 @@ module ActiveModel private def tokenize(value) - if value.kind_of?(String) - if options[:tokenizer] - options[:tokenizer].call(value) - elsif !value.encoding_aware? - value.mb_chars - end + if options[:tokenizer] && value.kind_of?(String) + options[:tokenizer].call(value) end || value end end diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index 34d447a0fa..bb9f9679fc 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -5,7 +5,7 @@ module ActiveModel class NumericalityValidator < EachValidator CHECKS = { :greater_than => :>, :greater_than_or_equal_to => :>=, :equal_to => :==, :less_than => :<, :less_than_or_equal_to => :<=, - :odd => :odd?, :even => :even? }.freeze + :odd => :odd?, :even => :even?, :other_than => :!= }.freeze RESERVED_OPTIONS = CHECKS.keys + [:only_integer] @@ -99,6 +99,7 @@ module ActiveModel # * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied value. # * <tt>:less_than</tt> - Specifies the value must be less than the supplied value. # * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less than or equal the supplied value. + # * <tt>:other_than</tt> - Specifies the value must be other than the supplied value. # * <tt>:odd</tt> - Specifies the value must be an odd number. # * <tt>:even</tt> - Specifies the value must be an even number. # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should @@ -107,7 +108,7 @@ module ActiveModel # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The # method, proc or string should return or evaluate to a true or false value. - # * <tt>:strict</tt> - Specifies whether validation should be strict. + # * <tt>:strict</tt> - Specifies whether validation should be strict. # See <tt>ActiveModel::Validation#validates!</tt> for more information # # The following checks can also be supplied with a proc or a symbol which corresponds to a method: diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb index 90f9b78334..0c6e49bee2 100644 --- a/activemodel/test/cases/attribute_methods_test.rb +++ b/activemodel/test/cases/attribute_methods_test.rb @@ -133,37 +133,6 @@ class AttributeMethodsTest < ActiveModel::TestCase assert_equal "value of foo bar", ModelWithAttributesWithSpaces.new.send(:'foo bar') end - test '#define_attr_method generates attribute method' do - assert_deprecated do - ModelWithAttributes.define_attr_method(:bar, 'bar') - end - - assert_respond_to ModelWithAttributes, :bar - - assert_deprecated do - assert_equal "original bar", ModelWithAttributes.original_bar - end - - assert_equal "bar", ModelWithAttributes.bar - ActiveSupport::Deprecation.silence do - ModelWithAttributes.define_attr_method(:bar) - end - assert !ModelWithAttributes.bar - end - - test '#define_attr_method generates attribute method with invalid identifier characters' do - ActiveSupport::Deprecation.silence do - ModelWithWeirdNamesAttributes.define_attr_method(:'c?d', 'c?d') - end - - assert_respond_to ModelWithWeirdNamesAttributes, :'c?d' - - ActiveSupport::Deprecation.silence do - assert_equal "original c?d", ModelWithWeirdNamesAttributes.send('original_c?d') - end - assert_equal "c?d", ModelWithWeirdNamesAttributes.send('c?d') - end - test '#alias_attribute works with attributes with spaces in their names' do ModelWithAttributesWithSpaces.define_attribute_methods([:'foo bar']) ModelWithAttributesWithSpaces.alias_attribute(:'foo_bar', :'foo bar') diff --git a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb index 676937b5e1..3660b9b1e5 100644 --- a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb +++ b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'logger' +require 'active_support/logger' require 'active_support/core_ext/object/inclusion' class SanitizerTest < ActiveModel::TestCase @@ -28,7 +28,7 @@ class SanitizerTest < ActiveModel::TestCase test "debug mass assignment removal with LoggerSanitizer" do original_attributes = { 'first_name' => 'allowed', 'admin' => 'denied' } log = StringIO.new - self.logger = Logger.new(log) + self.logger = ActiveSupport::Logger.new(log) @logger_sanitizer.sanitize(original_attributes, @authorizer) assert_match(/admin/, log.string, "Should log removed attributes: #{log.string}") end diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb index 08f6169ca5..6742a4bab0 100644 --- a/activemodel/test/cases/validations/numericality_validation_test.rb +++ b/activemodel/test/cases/validations/numericality_validation_test.rb @@ -106,6 +106,13 @@ class NumericalityValidationTest < ActiveModel::TestCase valid!([2]) end + def test_validates_numericality_with_other_than + Topic.validates_numericality_of :approved, :other_than => 0 + + invalid!([0, 0.0]) + valid!([-1, 42]) + end + def test_validates_numericality_with_proc Topic.send(:define_method, :min_approved, lambda { 5 }) Topic.validates_numericality_of :approved, :greater_than_or_equal_to => Proc.new {|topic| topic.min_approved } diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index ee2811c2be..2c8ec3d4d1 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,33 @@ ## Rails 4.0.0 (unreleased) ## +* Added the `ActiveRecord::Model` module which can be included in a + class as an alternative to inheriting from `ActiveRecord::Base`: + + class Post + include ActiveRecord::Model + end + + Please note: + + * Up until now it has been safe to assume that all AR models are + descendants of `ActiveRecord::Base`. This is no longer a safe + assumption, but it may transpire that there are areas of the + code which still make this assumption. So there may be + 'teething difficulties' with this feature. (But please do try it + and report bugs.) + + * Plugins & libraries etc that add methods to `ActiveRecord::Base` + will not be compatible with `ActiveRecord::Model`. Those libraries + should add to `ActiveRecord::Model` instead (which is included in + `Base`), or better still, avoid monkey-patching AR and instead + provide a module that users can include where they need it. + + * To minimise the risk of conflicts with other code, it is + advisable to include `ActiveRecord::Model` early in your class + definition. + + *Jon Leighton* + * PostgreSQL hstore records can be created. * PostgreSQL hstore types are automatically deserialized from the database. @@ -36,7 +64,7 @@ * Implemented ActiveRecord::Relation#pluck method Method returns Array of column value from table under ActiveRecord model - + Client.pluck(:id) *Bogdan Gusiev* @@ -53,7 +81,7 @@ Post.find(1) Post.connection.close }.join - + Only people who spawn threads in their application code need to worry about this change. @@ -152,7 +180,7 @@ during :reject_if => :all_blank (fixes #2937) *Aaron Christy* - + ## Rails 3.1.3 (unreleased) ## * Perf fix: If we're deleting all records in an association, don't add a IN(..) clause diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc index 70922ef864..6f9f63ceee 100644 --- a/activerecord/README.rdoc +++ b/activerecord/README.rdoc @@ -208,7 +208,9 @@ Source code can be downloaded as part of the Rails project on GitHub == License -Active Record is released under the MIT license. +Active Record is released under the MIT license: + +* http://www.opensource.org/licenses/MIT == Support diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 2463f3951f..4d143146d5 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -57,6 +57,8 @@ module ActiveRecord autoload :Base autoload :Callbacks + autoload :Configuration + autoload :Core autoload :CounterCache autoload :DynamicMatchers autoload :DynamicFinderMatch @@ -67,6 +69,7 @@ module ActiveRecord autoload :Integration autoload :Migration autoload :Migrator, 'active_record/migration' + autoload :Model autoload :ModelSchema autoload :NestedAttributes autoload :Observer @@ -105,7 +108,6 @@ module ActiveRecord autoload :TimeZoneConversion autoload :Write autoload :Serialization - autoload :DeprecatedUnderscoreRead end end diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 861dda618a..7887d59aad 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -230,13 +230,8 @@ module ActiveRecord end def build_record(attributes, options) - attributes = (attributes || {}).reverse_merge(creation_attributes) - reflection.build_association(attributes, options) do |record| - record.assign_attributes( - create_scope.except(*record.changed), - :without_protection => true - ) + record.assign_attributes(create_scope.except(*record.changed), :without_protection => true) end end end diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 650fa3fc42..44b0956e4e 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -16,7 +16,6 @@ module ActiveRecord include TimeZoneConversion include Dirty include Serialization - include DeprecatedUnderscoreRead # 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)). @@ -35,48 +34,26 @@ module ActiveRecord # accessors, mutators and query methods. def define_attribute_methods return if attribute_methods_generated? - - if base_class == self - super(column_names) - @attribute_methods_generated = true - else - base_class.define_attribute_methods - end + superclass.define_attribute_methods unless self == base_class + super(column_names) + @attribute_methods_generated = true end def attribute_methods_generated? - if base_class == self - @attribute_methods_generated ||= false - else - base_class.attribute_methods_generated? - end - end - - def generated_attribute_methods - @generated_attribute_methods ||= (base_class == self ? super : base_class.generated_attribute_methods) + @attribute_methods_generated ||= false end + # We will define the methods as instance methods, but will call them as singleton + # methods. This allows us to use method_defined? to check if the method exists, + # which is fast and won't give any false positives from the ancestors (because + # there are no ancestors). def generated_external_attribute_methods - @generated_external_attribute_methods ||= begin - if base_class == self - # We will define the methods as instance methods, but will call them as singleton - # methods. This allows us to use method_defined? to check if the method exists, - # which is fast and won't give any false positives from the ancestors (because - # there are no ancestors). - Module.new { extend self } - else - base_class.generated_external_attribute_methods - end - end + @generated_external_attribute_methods ||= Module.new { extend self } end def undefine_attribute_methods - if base_class == self - super - @attribute_methods_generated = false - else - base_class.undefine_attribute_methods - end + super if attribute_methods_generated? + @attribute_methods_generated = false end def instance_method_already_implemented?(method_name) @@ -84,19 +61,31 @@ module ActiveRecord raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" end - super + if active_record_super == Base + super + else + # If B < A and A defines its own attribute method, then we don't want to overwrite that. + defined = method_defined_within?(method_name, superclass, superclass.generated_attribute_methods) + defined && !ActiveRecord::Base.method_defined?(method_name) || super + end end # A method name is 'dangerous' if it is already defined by Active Record, but # not by any ancestors. (So 'puts' is not dangerous but 'save' is.) - def dangerous_attribute_method?(method_name) - active_record = ActiveRecord::Base - superclass = ActiveRecord::Base.superclass - - (active_record.method_defined?(method_name) || - active_record.private_method_defined?(method_name)) && - !superclass.method_defined?(method_name) && - !superclass.private_method_defined?(method_name) + def dangerous_attribute_method?(name) + method_defined_within?(name, Base) + end + + def method_defined_within?(name, klass, sup = klass.superclass) + if klass.method_defined?(name) || klass.private_method_defined?(name) + if sup.method_defined?(name) || sup.private_method_defined?(name) + klass.instance_method(name).owner != sup.instance_method(name).owner + else + true + end + else + false + end end def attribute_method?(attribute) diff --git a/activerecord/lib/active_record/attribute_methods/deprecated_underscore_read.rb b/activerecord/lib/active_record/attribute_methods/deprecated_underscore_read.rb deleted file mode 100644 index 0eb0db65b1..0000000000 --- a/activerecord/lib/active_record/attribute_methods/deprecated_underscore_read.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'active_support/concern' -require 'active_support/deprecation' - -module ActiveRecord - module AttributeMethods - module DeprecatedUnderscoreRead - extend ActiveSupport::Concern - - included do - attribute_method_prefix "_" - end - - module ClassMethods - protected - - def define_method__attribute(attr_name) - # Do nothing, let it hit method missing instead. - end - end - - protected - - def _attribute(attr_name) - ActiveSupport::Deprecation.warn( - "You have called '_#{attr_name}'. This is deprecated. Please use " \ - "either '#{attr_name}' or read_attribute('#{attr_name}')." - ) - read_attribute(attr_name) - end - end - end -end diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 5d37088d98..a7785f8786 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -80,10 +80,6 @@ module ActiveRecord end end - def original_primary_key #:nodoc: - deprecated_original_property_getter :primary_key - end - # Sets the name of the primary key column. # # class Project < ActiveRecord::Base @@ -103,11 +99,6 @@ module ActiveRecord @primary_key = value && value.to_s @quoted_primary_key = nil end - - def set_primary_key(value = nil, &block) #:nodoc: - deprecated_property_setter :primary_key, value, block - @quoted_primary_key = nil - 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 77bf9d0905..7d2d1db4b5 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -5,10 +5,7 @@ module ActiveRecord ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date] - included do - cattr_accessor :attribute_types_cached_by_default, :instance_writer => false - self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT - end + Configuration.define :attribute_types_cached_by_default, ATTRIBUTE_TYPES_CACHED_BY_DEFAULT module ClassMethods # +cache_attributes+ allows you to declare which converted attribute values should @@ -30,15 +27,33 @@ module ActiveRecord end def undefine_attribute_methods - if base_class == self - generated_external_attribute_methods.module_eval do - instance_methods.each { |m| undef_method(m) } - end + generated_external_attribute_methods.module_eval do + instance_methods.each { |m| undef_method(m) } end super end + def type_cast_attribute(attr_name, attributes, cache = {}) #:nodoc: + return unless attr_name + attr_name = attr_name.to_s + + if generated_external_attribute_methods.method_defined?(attr_name) + if attributes.has_key?(attr_name) || attr_name == 'id' + generated_external_attribute_methods.send(attr_name, attributes[attr_name], attributes, cache, attr_name) + end + elsif !attribute_methods_generated? + # If we haven't generated the caster methods yet, do that and + # then try again + define_attribute_methods + type_cast_attribute(attr_name, attributes, cache) + else + # If we get here, the attribute has no associated DB column, so + # just return it verbatim. + attributes[attr_name] + end + end + protected # We want to generate the methods via module_eval rather than define_method, # because define_method is slower on dispatch and uses more memory (because it @@ -105,25 +120,7 @@ module ActiveRecord # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example, # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). def read_attribute(attr_name) - return unless attr_name - - attr_name = attr_name.to_s - methods = self.class.generated_external_attribute_methods - - if methods.method_defined?(attr_name) - if @attributes.has_key?(attr_name) || attr_name == 'id' - methods.send(attr_name, @attributes[attr_name], @attributes, @attributes_cache, attr_name) - end - elsif !self.class.attribute_methods_generated? - # If we haven't generated the caster methods yet, do that and - # then try again - self.class.define_attribute_methods - read_attribute(attr_name) - else - # If we get here, the attribute has no associated DB column, so - # just return it verbatim. - @attributes[attr_name] - end + self.class.type_cast_attribute(attr_name, @attributes, @attributes_cache) end private diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 0a4432506f..2ffd91f796 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -58,6 +58,18 @@ module ActiveRecord self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder) end + def initialize_attributes(attributes) #:nodoc: + super + + serialized_attributes.each do |key, coder| + if attributes.key?(key) + attributes[key] = Attribute.new(coder, attributes[key], :serialized) + end + end + + attributes + end + private def attribute_cast_code(attr_name) @@ -69,14 +81,6 @@ module ActiveRecord end end - def set_serialized_attributes - self.class.serialized_attributes.each do |key, coder| - if @attributes.key?(key) - @attributes[key] = Attribute.new(coder, @attributes[key], :serialized) - end - end - end - def type_cast_attribute_for_write(column, value) if column && coder = self.class.serialized_attributes[column.name] Attribute.new(coder, value, :unserialized) 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 17cf34cdf6..5e5392441b 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -6,10 +6,9 @@ module ActiveRecord module TimeZoneConversion extend ActiveSupport::Concern - included do - cattr_accessor :time_zone_aware_attributes, :instance_writer => false - self.time_zone_aware_attributes = false + Configuration.define :time_zone_aware_attributes, false + included do class_attribute :skip_time_zone_conversion_for_attributes, :instance_writer => false self.skip_time_zone_conversion_for_attributes = [] end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 432a40ea54..6085df7d9f 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -327,386 +327,10 @@ module ActiveRecord #:nodoc: # So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all # instances in the current object space. class Base - ## - # :singleton-method: - # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, - # which is then passed on to any new database connections made and which can be retrieved on both - # a class and instance level by calling +logger+. - cattr_accessor :logger, :instance_writer => false - - ## - # :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' - # } - # } - cattr_accessor :configurations, :instance_writer => false - @@configurations = {} - - ## - # :singleton-method: - # Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling - # dates and times from the database. This is set to :local by default. - cattr_accessor :default_timezone, :instance_writer => false - @@default_timezone = :local - - ## - # :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. - cattr_accessor :schema_format , :instance_writer => false - @@schema_format = :ruby - - ## - # :singleton-method: - # Specify whether or not to use timestamps for migration versions - cattr_accessor :timestamped_migrations , :instance_writer => false - @@timestamped_migrations = true - - class << self # Class methods - def inherited(child_class) #:nodoc: - # force attribute methods to be higher in inheritance hierarchy than other generated methods - child_class.generated_attribute_methods - child_class.generated_feature_methods - super - end - - def generated_feature_methods - @generated_feature_methods ||= begin - mod = const_set(:GeneratedFeatureMethods, Module.new) - include mod - mod - end - end - - # Returns a string like 'Post(id:integer, title:string, body:text)' - def inspect - if self == Base - super - elsif abstract_class? - "#{super}(abstract)" - elsif table_exists? - attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', ' - "#{super}(#{attr_list})" - else - "#{super}(Table doesn't exist)" - end - end - - # Overwrite the default class equality method to provide support for association proxies. - def ===(object) - object.is_a?(self) - end - - def arel_table - @arel_table ||= Arel::Table.new(table_name, arel_engine) - end - - def arel_engine - @arel_engine ||= begin - if self == ActiveRecord::Base - ActiveRecord::Base - else - connection_handler.connection_pools[name] ? self : superclass.arel_engine - end - end - end - - private - - def relation #:nodoc: - @relation ||= Relation.new(self, arel_table) - - if finder_needs_type_condition? - @relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name) - else - @relation - end - end - end - - public - # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with - # attributes but not yet saved (pass a hash with key names matching the associated table column names). - # In both instances, valid attribute keys are determined by the column names of the associated table -- - # hence you can't have attributes that aren't part of the table columns. - # - # +initialize+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options - # in the +options+ parameter. - # - # ==== Examples - # # Instantiates a single new object - # User.new(:first_name => 'Jamie') - # - # # Instantiates a single new object using the :admin mass-assignment security role - # User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin) - # - # # Instantiates a single new object bypassing mass-assignment security - # User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true) - def initialize(attributes = nil, options = {}) - @attributes = attributes_from_column_definition - @association_cache = {} - @aggregation_cache = {} - @attributes_cache = {} - @new_record = true - @readonly = false - @destroyed = false - @marked_for_destruction = false - @previously_changed = {} - @changed_attributes = {} - @relation = nil - - ensure_proper_type - set_serialized_attributes - - populate_with_current_scope_attributes - - assign_attributes(attributes, options) if attributes - - yield self if block_given? - run_callbacks :initialize - end - - # Initialize an empty model object from +coder+. +coder+ must contain - # the attributes necessary for initializing an empty model object. For - # example: - # - # class Post < ActiveRecord::Base - # end - # - # post = Post.allocate - # post.init_with('attributes' => { 'title' => 'hello world' }) - # post.title # => 'hello world' - def init_with(coder) - @attributes = coder['attributes'] - @relation = nil - - set_serialized_attributes - - @attributes_cache, @previously_changed, @changed_attributes = {}, {}, {} - @association_cache = {} - @aggregation_cache = {} - @readonly = @destroyed = @marked_for_destruction = false - @new_record = false - run_callbacks :find - run_callbacks :initialize - - self - end - - # Duped objects have no id assigned and are treated as new records. Note - # that this is a "shallow" copy as it copies the object's attributes - # only, not its associations. The extent of a "deep" copy is application - # specific and is therefore left to the application to implement according - # to its need. - # The dup method does not preserve the timestamps (created|updated)_(at|on). - def initialize_dup(other) - cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast) - cloned_attributes.delete(self.class.primary_key) - - @attributes = cloned_attributes - - _run_after_initialize_callbacks if respond_to?(:_run_after_initialize_callbacks) - - @changed_attributes = {} - attributes_from_column_definition.each do |attr, orig_value| - @changed_attributes[attr] = orig_value if field_changed?(attr, orig_value, @attributes[attr]) - end - - @aggregation_cache = {} - @association_cache = {} - @attributes_cache = {} - @new_record = true - - ensure_proper_type - populate_with_current_scope_attributes - super - end - - # Backport dup from 1.9 so that initialize_dup() gets called - unless Object.respond_to?(:initialize_dup) - def dup # :nodoc: - copy = super - copy.initialize_dup(self) - copy - end - end - - # Populate +coder+ with attributes about this record that should be - # serialized. The structure of +coder+ defined in this method is - # guaranteed to match the structure of +coder+ passed to the +init_with+ - # method. - # - # Example: - # - # class Post < ActiveRecord::Base - # end - # coder = {} - # Post.new.encode_with(coder) - # coder # => { 'id' => nil, ... } - def encode_with(coder) - coder['attributes'] = attributes - end - - # Returns true if +comparison_object+ is the same exact object, or +comparison_object+ - # is of the same type and +self+ has an ID and it is equal to +comparison_object.id+. - # - # Note that new records are different from any other record by definition, unless the - # other record is the receiver itself. Besides, if you fetch existing records with - # +select+ and leave the ID out, you're on your own, this predicate will return false. - # - # Note also that destroying a record preserves its ID in the model instance, so deleted - # models are still comparable. - def ==(comparison_object) - super || - comparison_object.instance_of?(self.class) && - id.present? && - comparison_object.id == id - end - alias :eql? :== - - # Delegates to id in order to allow two records of the same type and id to work with something like: - # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ] - def hash - id.hash - end - - # Freeze the attributes hash such that associations are still accessible, even on destroyed records. - def freeze - @attributes.freeze; self - end - - # Returns +true+ if the attributes hash has been frozen. - def frozen? - @attributes.frozen? - end - - # Allows sort on objects - def <=>(other_object) - if other_object.is_a?(self.class) - self.to_key <=> other_object.to_key - else - nil - end - end - - # Returns +true+ if the record is read only. Records loaded through joins with piggy-back - # attributes will be marked as read only since they cannot be saved. - def readonly? - @readonly - end - - # Marks this record as read only. - def readonly! - @readonly = true - end - - # Returns the contents of the record as a nicely formatted string. - def inspect - inspection = if @attributes - self.class.column_names.collect { |name| - if has_attribute?(name) - "#{name}: #{attribute_for_inspect(name)}" - end - }.compact.join(", ") - else - "not initialized" - end - "#<#{self.class} #{inspection}>" - end - - # Hackery to accomodate Syck. Remove for 4.0. - def to_yaml(opts = {}) #:nodoc: - if YAML.const_defined?(:ENGINE) && !YAML::ENGINE.syck? - super - else - coder = {} - encode_with(coder) - YAML.quick_emit(self, opts) do |out| - out.map(taguri, to_yaml_style) do |map| - coder.each { |k, v| map.add(k, v) } - end - end - end - end - - # Hackery to accomodate Syck. Remove for 4.0. - def yaml_initialize(tag, coder) #:nodoc: - init_with(coder) - end - - private - - # Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements - # of the array, and then rescues from the possible NoMethodError. If those elements are - # ActiveRecord::Base's, then this triggers the various method_missing's that we have, - # which significantly impacts upon performance. - # - # So we can avoid the method_missing hit by explicitly defining #to_ary as nil here. - # - # See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary/ - def to_ary # :nodoc: - nil - end - - include ActiveRecord::Persistence - extend ActiveModel::Naming - extend QueryCache::ClassMethods - extend ActiveSupport::Benchmarkable - extend ActiveSupport::DescendantsTracker - - extend Querying - include ReadonlyAttributes - include ModelSchema - extend Translation - include Inheritance - include Scoping - extend DynamicMatchers - include Sanitization - include Integration - include AttributeAssignment - include ActiveModel::Conversion - include Validations - extend CounterCache - include Locking::Optimistic, Locking::Pessimistic - include AttributeMethods - include Callbacks, ActiveModel::Observing, Timestamp - include Associations - include IdentityMap - include ActiveModel::SecurePassword - extend Explain - - # AutosaveAssociation needs to be included before Transactions, because we want - # #save_with_autosave_associations to be wrapped inside a transaction. - include AutosaveAssociation, NestedAttributes - include Aggregations, Transactions, Reflection, Serialization, Store + include ActiveRecord::Model + self.connection_handler = ConnectionAdapters::ConnectionHandler.new end end require 'active_record/connection_adapters/abstract/connection_specification' -ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base) +ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Model::DeprecationProxy) diff --git a/activerecord/lib/active_record/configuration.rb b/activerecord/lib/active_record/configuration.rb new file mode 100644 index 0000000000..d58ed82258 --- /dev/null +++ b/activerecord/lib/active_record/configuration.rb @@ -0,0 +1,36 @@ +require 'active_support/concern' + +module ActiveRecord + # This module allows configuration options to be specified in a way such that + # ActiveRecord::Base and ActiveRecord::Model will have access to the same value, + # and will automatically get the appropriate readers and writers defined. + # + # In the future, we should probably move away from defining global config + # directly on ActiveRecord::Base / ActiveRecord::Model. + module Configuration #:nodoc: + extend ActiveSupport::Concern + + module ClassMethods + end + + def self.define(name, default = nil) + singleton_class.send(:attr_accessor, name) + + [self, ClassMethods].each do |klass| + klass.class_eval <<-CODE, __FILE__, __LINE__ + def #{name} + ActiveRecord::Configuration.#{name} + end + CODE + end + + ClassMethods.class_eval <<-CODE, __FILE__, __LINE__ + def #{name}=(val) + ActiveRecord::Configuration.#{name} = val + end + CODE + + send("#{name}=", default) unless default.nil? + end + end +end 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 698da34d26..5749d45a18 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -162,34 +162,6 @@ module ActiveRecord end end - def columns - with_connection do |c| - c.schema_cache.columns - end - end - deprecate :columns - - def columns_hash - with_connection do |c| - c.schema_cache.columns_hash - end - end - deprecate :columns_hash - - def primary_keys - with_connection do |c| - c.schema_cache.primary_keys - end - end - deprecate :primary_keys - - def clear_cache! - with_connection do |c| - c.schema_cache.clear! - end - end - deprecate :clear_cache! - # Return any checked-out connections back to the pool by threads that # are no longer alive. def clear_stale_cached_connections! @@ -399,7 +371,7 @@ connection. For example: ActiveRecord::Base.connection.close pool = @class_to_pool[klass.name] return pool if pool return nil if ActiveRecord::Base == klass - retrieve_connection_pool klass.superclass + retrieve_connection_pool klass.active_record_super end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb index 7145dc0692..63e4020113 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb @@ -1,5 +1,7 @@ +require 'active_support/core_ext/module/delegation' + module ActiveRecord - class Base + module Core class ConnectionSpecification #:nodoc: attr_reader :config, :adapter_method def initialize (config, adapter_method) @@ -75,12 +77,6 @@ module ActiveRecord end end - ## - # :singleton-method: - # The connection handler - class_attribute :connection_handler, :instance_writer => false - self.connection_handler = ConnectionAdapters::ConnectionHandler.new - # Returns the connection currently associated with the class. This can # also be used to "borrow" the connection to do database work that isn't # easily done without going straight to SQL. @@ -88,53 +84,53 @@ module ActiveRecord self.class.connection end - # Establishes the connection to the database. Accepts a hash as input where - # the <tt>:adapter</tt> key must be specified with the name of a database adapter (in lower-case) - # example for regular databases (MySQL, Postgresql, etc): - # - # ActiveRecord::Base.establish_connection( - # :adapter => "mysql", - # :host => "localhost", - # :username => "myuser", - # :password => "mypass", - # :database => "somedatabase" - # ) - # - # Example for SQLite database: - # - # ActiveRecord::Base.establish_connection( - # :adapter => "sqlite", - # :database => "path/to/dbfile" - # ) - # - # Also accepts keys as strings (for parsing from YAML for example): - # - # ActiveRecord::Base.establish_connection( - # "adapter" => "sqlite", - # "database" => "path/to/dbfile" - # ) - # - # Or a URL: - # - # ActiveRecord::Base.establish_connection( - # "postgres://myuser:mypass@localhost/somedatabase" - # ) - # - # The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError - # may be returned on an error. - def self.establish_connection(spec = ENV["DATABASE_URL"]) - resolver = ConnectionSpecification::Resolver.new spec, configurations - spec = resolver.spec - - unless respond_to?(spec.adapter_method) - raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter" - end + module ClassMethods + # Establishes the connection to the database. Accepts a hash as input where + # the <tt>:adapter</tt> key must be specified with the name of a database adapter (in lower-case) + # example for regular databases (MySQL, Postgresql, etc): + # + # ActiveRecord::Base.establish_connection( + # :adapter => "mysql", + # :host => "localhost", + # :username => "myuser", + # :password => "mypass", + # :database => "somedatabase" + # ) + # + # Example for SQLite database: + # + # ActiveRecord::Base.establish_connection( + # :adapter => "sqlite", + # :database => "path/to/dbfile" + # ) + # + # Also accepts keys as strings (for parsing from YAML for example): + # + # ActiveRecord::Base.establish_connection( + # "adapter" => "sqlite", + # "database" => "path/to/dbfile" + # ) + # + # Or a URL: + # + # ActiveRecord::Base.establish_connection( + # "postgres://myuser:mypass@localhost/somedatabase" + # ) + # + # The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError + # may be returned on an error. + def establish_connection(spec = ENV["DATABASE_URL"]) + resolver = ConnectionSpecification::Resolver.new spec, configurations + spec = resolver.spec + + unless respond_to?(spec.adapter_method) + raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter" + end - remove_connection - connection_handler.establish_connection name, spec - end + remove_connection + connection_handler.establish_connection name, spec + end - class << self # Returns the connection currently associated with the class. This can # also be used to "borrow" the connection to do database work unrelated # to any of the specific Active Records. diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 626571a948..e51796871a 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -4,9 +4,9 @@ gem 'mysql2', '~> 0.3.10' require 'mysql2' module ActiveRecord - class Base + module Core::ClassMethods # Establishes a connection to the database that's used by all Active Record objects. - def self.mysql2_connection(config) + def mysql2_connection(config) config[:username] = 'root' if config[:username].nil? if Mysql2::Client.const_defined? :FOUND_ROWS diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index f092edecda..901d8422f2 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -18,9 +18,9 @@ class Mysql end module ActiveRecord - class Base + module Core::ClassMethods # Establishes a connection to the database that's used by all Active Record objects. - def self.mysql_connection(config) # :nodoc: + def mysql_connection(config) # :nodoc: config = config.symbolize_keys host = config[:host] port = config[:port] @@ -224,52 +224,48 @@ module ActiveRecord @statements.clear end - if "<3".respond_to?(:encode) - # Taken from here: - # https://github.com/tmtm/ruby-mysql/blob/master/lib/mysql/charset.rb - # Author: TOMITA Masahiro <tommy@tmtm.org> - ENCODINGS = { - "armscii8" => nil, - "ascii" => Encoding::US_ASCII, - "big5" => Encoding::Big5, - "binary" => Encoding::ASCII_8BIT, - "cp1250" => Encoding::Windows_1250, - "cp1251" => Encoding::Windows_1251, - "cp1256" => Encoding::Windows_1256, - "cp1257" => Encoding::Windows_1257, - "cp850" => Encoding::CP850, - "cp852" => Encoding::CP852, - "cp866" => Encoding::IBM866, - "cp932" => Encoding::Windows_31J, - "dec8" => nil, - "eucjpms" => Encoding::EucJP_ms, - "euckr" => Encoding::EUC_KR, - "gb2312" => Encoding::EUC_CN, - "gbk" => Encoding::GBK, - "geostd8" => nil, - "greek" => Encoding::ISO_8859_7, - "hebrew" => Encoding::ISO_8859_8, - "hp8" => nil, - "keybcs2" => nil, - "koi8r" => Encoding::KOI8_R, - "koi8u" => Encoding::KOI8_U, - "latin1" => Encoding::ISO_8859_1, - "latin2" => Encoding::ISO_8859_2, - "latin5" => Encoding::ISO_8859_9, - "latin7" => Encoding::ISO_8859_13, - "macce" => Encoding::MacCentEuro, - "macroman" => Encoding::MacRoman, - "sjis" => Encoding::SHIFT_JIS, - "swe7" => nil, - "tis620" => Encoding::TIS_620, - "ucs2" => Encoding::UTF_16BE, - "ujis" => Encoding::EucJP_ms, - "utf8" => Encoding::UTF_8, - "utf8mb4" => Encoding::UTF_8, - } - else - ENCODINGS = Hash.new { |h,k| h[k] = k } - end + # Taken from here: + # https://github.com/tmtm/ruby-mysql/blob/master/lib/mysql/charset.rb + # Author: TOMITA Masahiro <tommy@tmtm.org> + ENCODINGS = { + "armscii8" => nil, + "ascii" => Encoding::US_ASCII, + "big5" => Encoding::Big5, + "binary" => Encoding::ASCII_8BIT, + "cp1250" => Encoding::Windows_1250, + "cp1251" => Encoding::Windows_1251, + "cp1256" => Encoding::Windows_1256, + "cp1257" => Encoding::Windows_1257, + "cp850" => Encoding::CP850, + "cp852" => Encoding::CP852, + "cp866" => Encoding::IBM866, + "cp932" => Encoding::Windows_31J, + "dec8" => nil, + "eucjpms" => Encoding::EucJP_ms, + "euckr" => Encoding::EUC_KR, + "gb2312" => Encoding::EUC_CN, + "gbk" => Encoding::GBK, + "geostd8" => nil, + "greek" => Encoding::ISO_8859_7, + "hebrew" => Encoding::ISO_8859_8, + "hp8" => nil, + "keybcs2" => nil, + "koi8r" => Encoding::KOI8_R, + "koi8u" => Encoding::KOI8_U, + "latin1" => Encoding::ISO_8859_1, + "latin2" => Encoding::ISO_8859_2, + "latin5" => Encoding::ISO_8859_9, + "latin7" => Encoding::ISO_8859_13, + "macce" => Encoding::MacCentEuro, + "macroman" => Encoding::MacRoman, + "sjis" => Encoding::SHIFT_JIS, + "swe7" => nil, + "tis620" => Encoding::TIS_620, + "ucs2" => Encoding::UTF_16BE, + "ujis" => Encoding::EucJP_ms, + "utf8" => Encoding::UTF_8, + "utf8mb4" => Encoding::UTF_8, + } # Get the client encoding for this database def client_encoding diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index d7adcdc5d4..74a9be99bd 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -7,9 +7,9 @@ gem 'pg', '~> 0.11' require 'pg' module ActiveRecord - class Base + module Core::ClassMethods # Establishes a connection to the database that's used by all Active Record objects - def self.postgresql_connection(config) # :nodoc: + def postgresql_connection(config) # :nodoc: config = config.symbolize_keys host = config[:host] port = config[:port] || 5432 @@ -876,7 +876,7 @@ module ActiveRecord # add info on sort order for columns (only desc order is explicitly specified, asc is the default) desc_order_columns = inddef.scan(/(\w+) DESC/).flatten orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {} - + column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders) end.compact end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 11bb457d03..ac3fb72b6e 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -4,9 +4,9 @@ gem 'sqlite3', '~> 1.3.5' require 'sqlite3' module ActiveRecord - class Base + module Core::ClassMethods # sqlite3 adapter reuses sqlite_connection. - def self.sqlite3_connection(config) # :nodoc: + def sqlite3_connection(config) # :nodoc: # Require database. unless config[:database] raise ArgumentError, "No database file specified. Missing argument: database" diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 55818b3fbf..69750a911d 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -16,7 +16,7 @@ module ActiveRecord end def binary_to_string(value) - if value.respond_to?(:force_encoding) && value.encoding != Encoding::ASCII_8BIT + if value.encoding != Encoding::ASCII_8BIT value = value.force_encoding(Encoding::ASCII_8BIT) end @@ -201,25 +201,17 @@ module ActiveRecord end end - if "<3".encoding_aware? - def type_cast(value, column) # :nodoc: - return value.to_f if BigDecimal === value - return super unless String === value - return super unless column && value + def type_cast(value, column) # :nodoc: + return value.to_f if BigDecimal === value + return super unless String === value + return super unless column && value - value = super - if column.type == :string && value.encoding == Encoding::ASCII_8BIT - @logger.error "Binary data inserted for `string` type on column `#{column.name}`" - value.encode! 'utf-8' - end - value - end - else - def type_cast(value, column) # :nodoc: - return super unless BigDecimal === value - - value.to_f + value = super + if column.type == :string && value.encoding == Encoding::ASCII_8BIT + @logger.error "Binary data inserted for `string` type on column `#{column.name}`" + value.encode! 'utf-8' end + value end # DATABASE STATEMENTS ====================================== diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb new file mode 100644 index 0000000000..4f118e46a9 --- /dev/null +++ b/activerecord/lib/active_record/core.rb @@ -0,0 +1,350 @@ +require 'active_support/concern' + +module ActiveRecord + module Core + extend ActiveSupport::Concern + + ## + # :singleton-method: + # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, + # which is then passed on to any new database connections made and which can be retrieved on both + # a class and instance level by calling +logger+. + Configuration.define :logger + + ## + # :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' + # } + # } + Configuration.define :configurations, {} + + ## + # :singleton-method: + # Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling + # dates and times from the database. This is set to :local by default. + Configuration.define :default_timezone, :local + + ## + # :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. + Configuration.define :schema_format, :ruby + + ## + # :singleton-method: + # Specify whether or not to use timestamps for migration versions + Configuration.define :timestamped_migrations, true + + included do + ## + # :singleton-method: + # The connection handler + class_attribute :connection_handler, :instance_writer => false + + initialize_generated_modules unless self == Base + end + + module ClassMethods + def inherited(child_class) #:nodoc: + child_class.initialize_generated_modules + super + end + + def initialize_generated_modules + # force attribute methods to be higher in inheritance hierarchy than other generated methods + generated_attribute_methods + generated_feature_methods + end + + def generated_feature_methods + @generated_feature_methods ||= begin + mod = const_set(:GeneratedFeatureMethods, Module.new) + include mod + mod + end + end + + # Returns a string like 'Post(id:integer, title:string, body:text)' + def inspect + if self == Base + super + elsif abstract_class? + "#{super}(abstract)" + elsif table_exists? + attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', ' + "#{super}(#{attr_list})" + else + "#{super}(Table doesn't exist)" + end + end + + # Overwrite the default class equality method to provide support for association proxies. + def ===(object) + object.is_a?(self) + end + + def arel_table + @arel_table ||= Arel::Table.new(table_name, arel_engine) + end + + def arel_engine + @arel_engine ||= begin + if self == ActiveRecord::Base + ActiveRecord::Base + else + connection_handler.connection_pools[name] ? self : active_record_super.arel_engine + end + end + end + + private + + def relation #:nodoc: + @relation ||= Relation.new(self, arel_table) + + if finder_needs_type_condition? + @relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name) + else + @relation + end + end + end + + # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with + # attributes but not yet saved (pass a hash with key names matching the associated table column names). + # In both instances, valid attribute keys are determined by the column names of the associated table -- + # hence you can't have attributes that aren't part of the table columns. + # + # +initialize+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options + # in the +options+ parameter. + # + # ==== Examples + # # Instantiates a single new object + # User.new(:first_name => 'Jamie') + # + # # Instantiates a single new object using the :admin mass-assignment security role + # User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin) + # + # # Instantiates a single new object bypassing mass-assignment security + # User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true) + def initialize(attributes = nil, options = {}) + @attributes = self.class.initialize_attributes(self.class.column_defaults.dup) + @association_cache = {} + @aggregation_cache = {} + @attributes_cache = {} + @new_record = true + @readonly = false + @destroyed = false + @marked_for_destruction = false + @previously_changed = {} + @changed_attributes = {} + @relation = nil + + ensure_proper_type + + populate_with_current_scope_attributes + + assign_attributes(attributes, options) if attributes + + yield self if block_given? + run_callbacks :initialize + end + + # Initialize an empty model object from +coder+. +coder+ must contain + # the attributes necessary for initializing an empty model object. For + # example: + # + # class Post < ActiveRecord::Base + # end + # + # post = Post.allocate + # post.init_with('attributes' => { 'title' => 'hello world' }) + # post.title # => 'hello world' + def init_with(coder) + @attributes = self.class.initialize_attributes(coder['attributes']) + @relation = nil + + @attributes_cache, @previously_changed, @changed_attributes = {}, {}, {} + @association_cache = {} + @aggregation_cache = {} + @readonly = @destroyed = @marked_for_destruction = false + @new_record = false + run_callbacks :find + run_callbacks :initialize + + self + end + + # Duped objects have no id assigned and are treated as new records. Note + # that this is a "shallow" copy as it copies the object's attributes + # only, not its associations. The extent of a "deep" copy is application + # specific and is therefore left to the application to implement according + # to its need. + # The dup method does not preserve the timestamps (created|updated)_(at|on). + def initialize_dup(other) + cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast) + cloned_attributes.delete(self.class.primary_key) + + @attributes = cloned_attributes + + _run_after_initialize_callbacks if respond_to?(:_run_after_initialize_callbacks) + + @changed_attributes = {} + self.class.column_defaults.each do |attr, orig_value| + @changed_attributes[attr] = orig_value if field_changed?(attr, orig_value, @attributes[attr]) + end + + @aggregation_cache = {} + @association_cache = {} + @attributes_cache = {} + @new_record = true + + ensure_proper_type + populate_with_current_scope_attributes + super + end + + # Populate +coder+ with attributes about this record that should be + # serialized. The structure of +coder+ defined in this method is + # guaranteed to match the structure of +coder+ passed to the +init_with+ + # method. + # + # Example: + # + # class Post < ActiveRecord::Base + # end + # coder = {} + # Post.new.encode_with(coder) + # coder # => { 'id' => nil, ... } + def encode_with(coder) + coder['attributes'] = attributes + end + + # Returns true if +comparison_object+ is the same exact object, or +comparison_object+ + # is of the same type and +self+ has an ID and it is equal to +comparison_object.id+. + # + # Note that new records are different from any other record by definition, unless the + # other record is the receiver itself. Besides, if you fetch existing records with + # +select+ and leave the ID out, you're on your own, this predicate will return false. + # + # Note also that destroying a record preserves its ID in the model instance, so deleted + # models are still comparable. + def ==(comparison_object) + super || + comparison_object.instance_of?(self.class) && + id.present? && + comparison_object.id == id + end + alias :eql? :== + + # Delegates to id in order to allow two records of the same type and id to work with something like: + # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ] + def hash + id.hash + end + + # Freeze the attributes hash such that associations are still accessible, even on destroyed records. + def freeze + @attributes.freeze; self + end + + # Returns +true+ if the attributes hash has been frozen. + def frozen? + @attributes.frozen? + end + + # Allows sort on objects + def <=>(other_object) + if other_object.is_a?(self.class) + self.to_key <=> other_object.to_key + else + nil + end + end + + # Returns +true+ if the record is read only. Records loaded through joins with piggy-back + # attributes will be marked as read only since they cannot be saved. + def readonly? + @readonly + end + + # Marks this record as read only. + def readonly! + @readonly = true + end + + # Returns the contents of the record as a nicely formatted string. + def inspect + inspection = if @attributes + self.class.column_names.collect { |name| + if has_attribute?(name) + "#{name}: #{attribute_for_inspect(name)}" + end + }.compact.join(", ") + else + "not initialized" + end + "#<#{self.class} #{inspection}>" + end + + # Hackery to accomodate Syck. Remove for 4.0. + def to_yaml(opts = {}) #:nodoc: + if YAML.const_defined?(:ENGINE) && !YAML::ENGINE.syck? + super + else + coder = {} + encode_with(coder) + YAML.quick_emit(self, opts) do |out| + out.map(taguri, to_yaml_style) do |map| + coder.each { |k, v| map.add(k, v) } + end + end + end + end + + # Hackery to accomodate Syck. Remove for 4.0. + def yaml_initialize(tag, coder) #:nodoc: + init_with(coder) + end + + private + + # Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements + # of the array, and then rescues from the possible NoMethodError. If those elements are + # ActiveRecord::Base's, then this triggers the various method_missing's that we have, + # which significantly impacts upon performance. + # + # So we can avoid the method_missing hit by explicitly defining #to_ary as nil here. + # + # See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary/ + def to_ary # :nodoc: + nil + end + end +end diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb index b64390250d..b5a67afd88 100644 --- a/activerecord/lib/active_record/explain.rb +++ b/activerecord/lib/active_record/explain.rb @@ -2,14 +2,9 @@ require 'active_support/core_ext/class/attribute' module ActiveRecord module Explain - def self.extended(base) - base.class_eval do - # If a query takes longer than these many seconds we log its query plan - # automatically. nil disables this feature. - class_attribute :auto_explain_threshold_in_seconds, :instance_writer => false - self.auto_explain_threshold_in_seconds = nil - end - end + # If a query takes longer than these many seconds we log its query plan + # automatically. nil disables this feature. + Configuration.define :auto_explain_threshold_in_seconds # 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/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index da72d1718e..65c7f3afbb 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -559,7 +559,7 @@ module ActiveRecord rows[table_name] = fixtures.map do |label, fixture| row = fixture.to_hash - if model_class && model_class < ActiveRecord::Base + if model_class && model_class < ActiveRecord::Model # fill in timestamp columns if they aren't specified and the model is set to record_timestamps if model_class.record_timestamps timestamp_column_names.each do |name| diff --git a/activerecord/lib/active_record/identity_map.rb b/activerecord/lib/active_record/identity_map.rb index b15b5a8133..680d9ffea0 100644 --- a/activerecord/lib/active_record/identity_map.rb +++ b/activerecord/lib/active_record/identity_map.rb @@ -111,13 +111,12 @@ module ActiveRecord # model object. def reinit_with(coder) @attributes_cache = {} - dirty = @changed_attributes.keys - @attributes.update(coder['attributes'].except(*dirty)) + dirty = @changed_attributes.keys + attributes = self.class.initialize_attributes(coder['attributes'].except(*dirty)) + @attributes.update(attributes) @changed_attributes.update(coder['attributes'].slice(*dirty)) @changed_attributes.delete_if{|k,v| v.eql? @attributes[k]} - set_serialized_attributes - run_callbacks :find self diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index de9461982a..ec57151d40 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -13,10 +13,12 @@ module ActiveRecord module ClassMethods # True if this isn't a concrete subclass needing a STI type condition. def descends_from_active_record? - if superclass.abstract_class? - superclass.descends_from_active_record? + sup = active_record_super + + if sup.abstract_class? + sup.descends_from_active_record? else - superclass == Base || !columns_hash.include?(inheritance_column) + sup == Base || !columns_hash.include?(inheritance_column) end end @@ -79,17 +81,34 @@ module ActiveRecord instance end + # If this class includes ActiveRecord::Model then it won't have a + # superclass. So this provides a way to get to the 'root' (ActiveRecord::Base), + # through inheritance hierarchy, ending in Base, whether or not that is + # actually an ancestor of the class. + # + # Mainly for internal use. + def active_record_super #:nodoc: + if self == Base || superclass && superclass < Model::Tag + superclass + else + Base + end + end + protected # 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) - if klass == Base || klass.superclass == Base || klass.superclass.abstract_class? - klass - elsif klass.superclass.nil? + unless klass < Model::Tag raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord" + end + + sup = klass.active_record_super + if klass == Base || sup == Base || sup.abstract_class? + klass else - class_of_active_record_descendant(klass.superclass) + class_of_active_record_descendant(sup) end end diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index ce0a165660..b80d01db81 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -48,10 +48,7 @@ module ActiveRecord module Optimistic extend ActiveSupport::Concern - included do - cattr_accessor :lock_optimistically, :instance_writer => false - self.lock_optimistically = true - end + Configuration.define :lock_optimistically, true def locking_enabled? #:nodoc: self.class.locking_enabled? @@ -64,21 +61,6 @@ module ActiveRecord send(lock_col + '=', previous_lock_value + 1) end - def attributes_from_column_definition - result = self.class.column_defaults.dup - - # If the locking column has no default value set, - # start the lock version at zero. Note we can't use - # <tt>locking_enabled?</tt> at this point as - # <tt>@attributes</tt> may not have been initialized yet. - - if result.key?(self.class.locking_column) && lock_optimistically - result[self.class.locking_column] ||= 0 - end - - result - end - def update(attribute_names = @attributes.keys) #:nodoc: return super unless locking_enabled? return 0 if attribute_names.empty? @@ -144,26 +126,18 @@ module ActiveRecord lock_optimistically && columns_hash[locking_column] end + # Set the column to use for optimistic locking. Defaults to +lock_version+. def locking_column=(value) @original_locking_column = @locking_column if defined?(@locking_column) @locking_column = value.to_s end - # Set the column to use for optimistic locking. Defaults to +lock_version+. - def set_locking_column(value = nil, &block) - deprecated_property_setter :locking_column, value, block - end - # The version column used for optimistic locking. Defaults to +lock_version+. def locking_column reset_locking_column unless defined?(@locking_column) @locking_column end - def original_locking_column #:nodoc: - deprecated_original_property_getter :locking_column - end - # Quote the column name used for optimistic locking. def quoted_locking_column connection.quote_column_name(locking_column) @@ -180,6 +154,18 @@ module ActiveRecord counters = counters.merge(locking_column => 1) if locking_enabled? super end + + # If the locking column has no default value set, + # start the lock version at zero. Note we can't use + # <tt>locking_enabled?</tt> at this point as + # <tt>@attributes</tt> may not have been initialized yet. + def initialize_attributes(attributes) #:nodoc: + if attributes.key?(locking_column) && lock_optimistically + attributes[locking_column] ||= 0 + end + + attributes + end end end end diff --git a/activerecord/lib/active_record/model.rb b/activerecord/lib/active_record/model.rb new file mode 100644 index 0000000000..f87be257db --- /dev/null +++ b/activerecord/lib/active_record/model.rb @@ -0,0 +1,89 @@ +require 'active_support/deprecation' + +module ActiveRecord + # <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: + # + # class Post + # include ActiveRecord::Model + # end + # + module Model + # So we can recognise an AR class even while self.included is being + # executed. (At that time, klass < Model == false.) + module Tag #:nodoc: + end + + def self.included(base) + return if base < Tag + + base.class_eval do + include Tag + + include Configuration + + include ActiveRecord::Persistence + extend ActiveModel::Naming + extend QueryCache::ClassMethods + extend ActiveSupport::Benchmarkable + extend ActiveSupport::DescendantsTracker + + extend Querying + include ReadonlyAttributes + include ModelSchema + extend Translation + include Inheritance + include Scoping + extend DynamicMatchers + include Sanitization + include Integration + include AttributeAssignment + include ActiveModel::Conversion + include Validations + extend CounterCache + include Locking::Optimistic, Locking::Pessimistic + include AttributeMethods + include Callbacks, ActiveModel::Observing, Timestamp + include Associations + include IdentityMap + include ActiveModel::SecurePassword + extend Explain + + # AutosaveAssociation needs to be included before Transactions, because we want + # #save_with_autosave_associations to be wrapped inside a transaction. + include AutosaveAssociation, NestedAttributes + include Aggregations, Transactions, Reflection, Serialization, Store + + include Core + + self.connection_handler = Base.connection_handler + end + end + + module DeprecationProxy #:nodoc: + class << self + instance_methods.each { |m| undef_method m unless m =~ /^__|^object_id$|^instance_eval$/ } + + def method_missing(name, *args, &block) + if Model.respond_to?(name) + Model.send(name, *args, &block) + else + ActiveSupport::Deprecation.warn( + "The object passed to the active_record load hook was previously ActiveRecord::Base " \ + "(a Class). Now it is ActiveRecord::Model (a Module). You have called `#{name}' which " \ + "is only defined on ActiveRecord::Base. Please change your code so that it works with " \ + "a module rather than a class. (Model is included in Base, so anything added to Model " \ + "will be available on Base as well.)" + ) + Base.send(name, *args, &block) + end + end + + alias send method_missing + end + end + end + + # 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 36417d89f7..adf85c6436 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -1,20 +1,20 @@ require 'active_support/concern' +require 'active_support/core_ext/class/attribute_accessors' module ActiveRecord module ModelSchema extend ActiveSupport::Concern - included do - ## - # :singleton-method: - # Accessor for the prefix type that will be prepended to every primary key column name. - # The options are :table_name and :table_name_with_underscore. If the first is specified, - # the Product class will look for "productid" instead of "id" as the primary column. If the - # latter is specified, the Product class will look for "product_id" instead of "id". Remember - # that this is a global setting for all Active Records. - cattr_accessor :primary_key_prefix_type, :instance_writer => false - self.primary_key_prefix_type = nil + ## + # :singleton-method: + # Accessor for the prefix type that will be prepended to every primary key column name. + # The options are :table_name and :table_name_with_underscore. If the first is specified, + # the Product class will look for "productid" instead of "id" as the primary column. If the + # latter is specified, the Product class will look for "product_id" instead of "id". Remember + # that this is a global setting for all Active Records. + Configuration.define :primary_key_prefix_type + included do ## # :singleton-method: # Accessor for the name of the prefix string to prepend to every table name. So if set @@ -105,10 +105,6 @@ module ActiveRecord @table_name end - def original_table_name #:nodoc: - deprecated_original_property_getter :table_name - end - # Sets the table name explicitly. Example: # # class Project < ActiveRecord::Base @@ -125,13 +121,6 @@ module ActiveRecord @relation = Relation.new(self, arel_table) end - def set_table_name(value = nil, &block) #:nodoc: - deprecated_property_setter :table_name, value, block - @quoted_table_name = nil - @arel_table = nil - @relation = Relation.new(self, arel_table) - end - # Returns a quoted version of the table name, used to construct SQL statements. def quoted_table_name @quoted_table_name ||= connection.quote_table_name(table_name) @@ -139,10 +128,10 @@ module ActiveRecord # Computes the table name, (re)sets it internally, and returns it. def reset_table_name #:nodoc: - if superclass.abstract_class? - self.table_name = superclass.table_name || compute_table_name + if active_record_super.abstract_class? + self.table_name = active_record_super.table_name || compute_table_name elsif abstract_class? - self.table_name = superclass == Base ? nil : superclass.table_name + self.table_name = active_record_super == Base ? nil : active_record_super.table_name else self.table_name = compute_table_name end @@ -157,24 +146,16 @@ module ActiveRecord if self == Base 'type' else - (@inheritance_column ||= nil) || superclass.inheritance_column + (@inheritance_column ||= nil) || active_record_super.inheritance_column end end - def original_inheritance_column #:nodoc: - deprecated_original_property_getter :inheritance_column - end - # Sets the value of inheritance_column def inheritance_column=(value) @original_inheritance_column = inheritance_column @inheritance_column = value.to_s end - def set_inheritance_column(value = nil, &block) #:nodoc: - deprecated_property_setter :inheritance_column, value, block - end - def sequence_name if base_class == self @sequence_name ||= reset_sequence_name @@ -183,10 +164,6 @@ module ActiveRecord end end - def original_sequence_name #:nodoc: - deprecated_original_property_getter :sequence_name - end - def reset_sequence_name #:nodoc: self.sequence_name = connection.default_sequence_name(table_name, primary_key) end @@ -210,10 +187,6 @@ module ActiveRecord @sequence_name = value.to_s end - def set_sequence_name(value = nil, &block) #:nodoc: - deprecated_property_setter :sequence_name, value, block - end - # Indicates whether the table associated with this class exists def table_exists? connection.schema_cache.table_exists?(table_name) @@ -318,7 +291,7 @@ module ActiveRecord base = base_class if self == base # Nested classes are prefixed with singular parent table name. - if parent < ActiveRecord::Base && !parent.abstract_class? + if parent < ActiveRecord::Model && !parent.abstract_class? contained = parent.table_name contained = contained.singularize if parent.pluralize_table_names contained += '_' @@ -329,34 +302,6 @@ module ActiveRecord base.table_name end end - - def deprecated_property_setter(property, value, block) - if block - ActiveSupport::Deprecation.warn( - "Calling set_#{property} is deprecated. If you need to lazily evaluate " \ - "the #{property}, define your own `self.#{property}` class method. You can use `super` " \ - "to get the default #{property} where you would have called `original_#{property}`." - ) - - define_attr_method property, value, false, &block - else - ActiveSupport::Deprecation.warn( - "Calling set_#{property} is deprecated. Please use `self.#{property} = 'the_name'` instead." - ) - - define_attr_method property, value, false - end - end - - def deprecated_original_property_getter(property) - ActiveSupport::Deprecation.warn("original_#{property} is deprecated. Define self.#{property} and call super instead.") - - if !instance_variable_defined?("@original_#{property}") && respond_to?("reset_#{property}") - send("reset_#{property}") - else - instance_variable_get("@original_#{property}") - end - end end end end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index a2fe21043f..09ee2ba61d 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -9,7 +9,7 @@ module ActiveRecord # Creates an object (or multiple objects) and saves it to the database, if validations pass. # The resulting object is returned whether the object was saved successfully to the database or not. # - # The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the + # The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the # attributes on the objects that are to be created. # # +create+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options @@ -59,8 +59,8 @@ module ActiveRecord @destroyed end - # Returns if the record is persisted, i.e. it's not a new record and it was - # not destroyed. + # Returns true if the record is persisted, i.e. it's not a new record and it was + # not destroyed, otherwise returns false. def persisted? !(new_record? || destroyed?) end @@ -209,7 +209,7 @@ module ActiveRecord # The following transaction covers any possible database side-effects of the # attributes assignment. For example, setting the IDs of a child collection. with_transaction_returning_status do - self.assign_attributes(attributes, options) + assign_attributes(attributes, options) save end end @@ -220,7 +220,7 @@ module ActiveRecord # The following transaction covers any possible database side-effects of the # attributes assignment. For example, setting the IDs of a child collection. with_transaction_returning_status do - self.assign_attributes(attributes, options) + assign_attributes(attributes, options) save! end end @@ -285,7 +285,7 @@ module ActiveRecord clear_association_cache IdentityMap.without do - fresh_object = self.class.unscoped { self.class.find(self.id, options) } + fresh_object = self.class.unscoped { self.class.find(id, options) } @attributes.update(fresh_object.instance_variable_get('@attributes')) end @@ -368,13 +368,5 @@ module ActiveRecord @new_record = false id end - - # Initializes the attributes array with keys matching the columns from the linked table and - # the values matching the corresponding default value of that column, so - # that a new instance, or one populated from a passed-in Hash, still has all the attributes - # that instances loaded from the database would. - def attributes_from_column_definition - self.class.column_defaults.dup - end end end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 199eee4359..de299dbe91 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -38,6 +38,7 @@ db_namespace = namespace :db do 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) diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 0f57e9831d..bf9b4bf1c9 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -168,7 +168,7 @@ module ActiveRecord # This method is designed to perform select by a single column as direct SQL query # Returns <tt>Array</tt> with values of the specified column name - # The values has same data type as column. + # The values has same data type as column. # # Examples: # @@ -177,9 +177,8 @@ module ActiveRecord # Person.where(:confirmed => true).limit(5).pluck(:id) # def pluck(column_name) - scope = self.select(column_name) - self.connection.select_values(scope.to_sql).map! do |value| - type_cast_using_column(value, column_for(column_name)) + klass.connection.select_all(select(column_name).arel).map! do |attributes| + klass.type_cast_attribute(attributes.keys.first, klass.initialize_attributes(attributes)) end end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 3c8e0f2052..311bf4dc0f 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -187,7 +187,7 @@ module ActiveRecord def exists?(id = false) return false if id.nil? - id = id.id if ActiveRecord::Base === id + id = id.id if ActiveRecord::Model === id join_dependency = construct_join_dependency_for_association_find relation = construct_relation_for_association_find(join_dependency) diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index a789f48725..eee198e760 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -22,7 +22,7 @@ module ActiveRecord value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty? attribute.in(value.arel.ast) when Array, ActiveRecord::Associations::CollectionProxy - values = value.to_a.map {|x| x.is_a?(ActiveRecord::Base) ? x.id : x} + values = value.to_a.map {|x| x.is_a?(ActiveRecord::Model) ? x.id : x} ranges, values = values.partition {|v| v.is_a?(Range) || v.is_a?(Arel::Relation)} array_predicates = ranges.map {|range| attribute.in(range)} @@ -41,7 +41,7 @@ module ActiveRecord array_predicates.inject {|composite, predicate| composite.or(predicate)} when Range, Arel::Relation attribute.in(value) - when ActiveRecord::Base + when ActiveRecord::Model attribute.eq(value.id) when Class # FIXME: I think we need to deprecate this behavior diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb index ffe9b08dce..5398a14fc6 100644 --- a/activerecord/lib/active_record/test_case.rb +++ b/activerecord/lib/active_record/test_case.rb @@ -13,13 +13,6 @@ module ActiveRecord ActiveRecord::IdentityMap.clear end - # Backport skip to Ruby 1.8. test/unit doesn't support it, so just - # make it a noop. - unless instance_methods.map(&:to_s).include?("skip") - def skip(message) - end - end - def assert_date_from_db(expected, actual, message = nil) # SybaseAdapter doesn't have a separate column type just for dates, # so the time is in the string and incorrectly formatted @@ -56,18 +49,5 @@ module ActiveRecord ensure ActiveRecord::SQLCounter.ignored_sql = prev_ignored_sql end - - def with_kcode(kcode) - if RUBY_VERSION < '1.9' - orig_kcode, $KCODE = $KCODE, kcode - begin - yield - ensure - $KCODE = orig_kcode - end - else - yield - end - end end end diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb index f6159deeeb..1509e34473 100644 --- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb +++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb @@ -3,7 +3,7 @@ require 'rails/generators/active_record' module ActiveRecord module Generators class MigrationGenerator < Base - argument :attributes, :type => :array, :default => [], :banner => "field:type field:type" + argument :attributes, :type => :array, :default => [], :banner => "field[:type][:index] field[:type][:index]" def create_migration_file set_local_assigns! diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb index ce8d7eed42..d084a00ed7 100644 --- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb +++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb @@ -2,14 +2,20 @@ class <%= migration_class_name %> < ActiveRecord::Migration <%- if migration_action == 'add' -%> def change <% attributes.each do |attribute| -%> - add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %> + add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %> + <%- if attribute.has_index? -%> + add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> + <%- end %> <%- end -%> end <%- else -%> def up <% attributes.each do |attribute| -%> <%- if migration_action -%> - <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type %><% end %> + <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type %><%= attribute.inject_options %><% end %> + <% if attribute.has_index? && migration_action == 'add' %> + add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> + <% end -%> <%- end -%> <%- end -%> end @@ -17,7 +23,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration def down <% attributes.reverse.each do |attribute| -%> <%- if migration_action -%> - <%= migration_action == 'add' ? 'remove' : 'add' %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'remove' %>, :<%= attribute.type %><% end %> + <%= migration_action == 'add' ? 'remove' : 'add' %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'remove' %>, :<%= attribute.type %><%= attribute.inject_options %><% end %> <%- end -%> <%- end -%> end diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb index f7caa43ac8..99a022461e 100644 --- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb +++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb @@ -3,7 +3,7 @@ require 'rails/generators/active_record' module ActiveRecord module Generators class ModelGenerator < Base - argument :attributes, :type => :array, :default => [], :banner => "field:type field:type" + argument :attributes, :type => :array, :default => [], :banner => "field[:type][:index] field[:type][:index]" check_class_collision @@ -26,6 +26,10 @@ module ActiveRecord template 'module.rb', File.join('app/models', "#{class_path.join('/')}.rb") if behavior == :invoke end + def attributes_with_index + attributes.select { |a| a.has_index? || (a.reference? && options[:indexes]) } + end + hook_for :test_framework protected diff --git a/activerecord/lib/rails/generators/active_record/model/templates/migration.rb b/activerecord/lib/rails/generators/active_record/model/templates/migration.rb index 851930344a..3a3cf86d73 100644 --- a/activerecord/lib/rails/generators/active_record/model/templates/migration.rb +++ b/activerecord/lib/rails/generators/active_record/model/templates/migration.rb @@ -2,16 +2,14 @@ class <%= migration_class_name %> < ActiveRecord::Migration def change create_table :<%= table_name %> do |t| <% attributes.each do |attribute| -%> - t.<%= attribute.type %> :<%= attribute.name %> + t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %> <% end -%> <% if options[:timestamps] %> t.timestamps <% end -%> end -<% if options[:indexes] -%> -<% attributes.select {|attr| attr.reference? }.each do |attribute| -%> - add_index :<%= table_name %>, :<%= attribute.name %>_id -<% end -%> +<% attributes_with_index.each do |attribute| -%> + add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> <% end -%> end end diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb index 146b77a95c..7fe2c02c04 100644 --- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb @@ -17,11 +17,7 @@ module ActiveRecord end def test_client_encoding - if "<3".respond_to?(:encoding) - assert_equal Encoding::UTF_8, @conn.client_encoding - else - assert_equal 'utf8', @conn.client_encoding - end + assert_equal Encoding::UTF_8, @conn.client_encoding end def test_exec_insert_number @@ -41,13 +37,11 @@ module ActiveRecord value = result.rows.last.last - if "<3".respond_to?(:encoding) - # FIXME: this should probably be inside the mysql AR adapter? - value.force_encoding(@conn.client_encoding) + # FIXME: this should probably be inside the mysql AR adapter? + value.force_encoding(@conn.client_encoding) - # The strings in this file are utf-8, so transcode to utf-8 - value.encode!(Encoding::UTF_8) - end + # The strings in this file are utf-8, so transcode to utf-8 + value.encode!(Encoding::UTF_8) assert_equal str, value end diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 97b56d38d7..17bde6cb62 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -23,8 +23,6 @@ module ActiveRecord end def test_column_types - return skip('only test encoding on 1.9') unless "<3".encoding_aware? - owner = Owner.create!(:name => "hello".encode('ascii-8bit')) owner.reload select = Owner.columns.map { |c| "typeof(#{c.name})" }.join ', ' @@ -144,8 +142,6 @@ module ActiveRecord end def test_quote_binary_column_escapes_it - return unless "<3".respond_to?(:encode) - DualEncoding.connection.execute(<<-eosql) CREATE TABLE dual_encodings ( id integer PRIMARY KEY AUTOINCREMENT, @@ -159,9 +155,7 @@ module ActiveRecord assert_equal str, binary.data ensure - if "<3".respond_to?(:encode) - DualEncoding.connection.drop_table('dual_encodings') - end + DualEncoding.connection.drop_table('dual_encodings') end def test_execute diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb index 2cf9f89c3c..1e1958410c 100644 --- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb +++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb @@ -6,7 +6,6 @@ require 'models/comment' require 'models/category' require 'models/categorization' require 'models/tagging' -require 'active_support/core_ext/array/random_access' module Remembered extend ActiveSupport::Concern diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 1dc71ac4cc..3d759e64f8 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -1095,4 +1095,11 @@ class EagerAssociationTest < ActiveRecord::TestCase Post.includes(:comments).order(nil).where(:comments => {:body => "Thank you for the welcome"}).first end end + + def test_deep_including_through_habtm + posts = Post.find(:all, :include => {:categories => :categorizations}, :order => "posts.id") + assert_no_queries { assert_equal 2, posts[0].categories[0].categorizations.length } + assert_no_queries { assert_equal 1, posts[0].categories[1].categorizations.length } + assert_no_queries { assert_equal 2, posts[1].categories[0].categorizations.length } + end end diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb index 86a240d93c..375c207d20 100644 --- a/activerecord/test/cases/attribute_methods/read_test.rb +++ b/activerecord/test/cases/attribute_methods/read_test.rb @@ -14,8 +14,11 @@ module ActiveRecord def setup @klass = Class.new do + def self.superclass; Base; end + def self.active_record_super; Base; end def self.base_class; self; end + include ActiveRecord::Configuration include ActiveRecord::AttributeMethods def self.column_names diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 5e9f8028e9..b6cf26f978 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -102,7 +102,6 @@ class AttributeMethodsTest < ActiveRecord::TestCase def test_respond_to? topic = Topic.find(1) assert_respond_to topic, "title" - assert_respond_to topic, "_title" assert_respond_to topic, "title?" assert_respond_to topic, "title=" assert_respond_to topic, :title @@ -114,19 +113,12 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert !topic.respond_to?(:nothingness) end - def test_deprecated_underscore_method - topic = Topic.find(1) - assert_equal topic.title, assert_deprecated { topic._title } - end - def test_respond_to_with_custom_primary_key keyboard = Keyboard.create assert_not_nil keyboard.key_number assert_equal keyboard.key_number, keyboard.id assert keyboard.respond_to?('key_number') - assert keyboard.respond_to?('_key_number') assert keyboard.respond_to?('id') - assert keyboard.respond_to?('_id') end # Syck calls respond_to? before actually calling initialize @@ -719,6 +711,26 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal nil, Topic.new.read_attribute(nil) end + # If B < A, and A defines an accessor for 'foo', we don't want to override + # that by defining a 'foo' method in the generated methods module for B. + # (That module will be inserted between the two, e.g. [B, <GeneratedAttributes>, A].) + def test_inherited_custom_accessors + klass = Class.new(ActiveRecord::Base) do + self.table_name = "topics" + self.abstract_class = true + def title; "omg"; end + def title=(val); self.author_name = val; end + end + subklass = Class.new(klass) + [klass, subklass].each(&:define_attribute_methods) + + topic = subklass.find(1) + assert_equal "omg", topic.title + + topic.title = "lol" + assert_equal "lol", topic.author_name + end + private def cached_columns @cached_columns ||= time_related_columns_on_topic.map(&:name) diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index daef577c97..c465e9b556 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -76,6 +76,7 @@ class BasicsTest < ActiveRecord::TestCase assert(modules.index(Computer.generated_attribute_methods) > modules.index(Computer.generated_feature_methods), "generated_attribute_methods must be higher in inheritance hierarchy than generated_feature_methods") assert_not_equal Computer.generated_feature_methods, Post.generated_feature_methods + assert(modules.index(Computer.generated_attribute_methods) < modules.index(ActiveRecord::Base.ancestors[1])) end def test_column_names_are_escaped @@ -1233,6 +1234,27 @@ class BasicsTest < ActiveRecord::TestCase assert_equal(myobj, topic.content) end + def test_serialized_attribute_in_base_class + Topic.serialize("content", Hash) + + hash = { 'content1' => 'value1', 'content2' => 'value2' } + important_topic = ImportantTopic.create("content" => hash) + assert_equal(hash, important_topic.content) + + important_topic.reload + assert_equal(hash, important_topic.content) + end + + def test_serialized_attribute_declared_in_subclass + hash = { 'important1' => 'value1', 'important2' => 'value2' } + important_topic = ImportantTopic.create("important" => hash) + assert_equal(hash, important_topic.important) + + important_topic.reload + assert_equal(hash, important_topic.important) + assert_equal(hash, important_topic.read_attribute(:important)) + end + def test_serialized_time_attribute myobj = Time.local(2008,1,1,1,0) topic = Topic.create("content" => myobj).reload @@ -1357,22 +1379,6 @@ class BasicsTest < ActiveRecord::TestCase assert_equal author_name, Topic.find(topic.id).author_name end - if RUBY_VERSION < '1.9' - def test_quote_chars - with_kcode('UTF8') do - str = 'The Narrator' - topic = Topic.create(:author_name => str) - assert_equal str, topic.author_name - - assert_kind_of ActiveSupport::Multibyte.proxy_class, str.mb_chars - topic = Topic.find_by_author_name(str.mb_chars) - - assert_kind_of Topic, topic - assert_equal str, topic.author_name, "The right topic should have been found by name even with name passed as Chars" - end - end - end - def test_toggle_attribute assert !topics(:first).approved? topics(:first).toggle!(:approved) @@ -1399,17 +1405,6 @@ class BasicsTest < ActiveRecord::TestCase assert_equal dev, dev.reload end - def test_set_table_name_with_value - k = Class.new( ActiveRecord::Base ) - k.table_name = "foo" - assert_equal "foo", k.table_name - - assert_deprecated do - k.set_table_name "bar" - end - assert_equal "bar", k.table_name - end - def test_switching_between_table_name assert_difference("GoodJoke.count") do Joke.table_name = "cold_jokes" @@ -1432,17 +1427,6 @@ class BasicsTest < ActiveRecord::TestCase assert_equal klass.connection.quote_table_name("bar"), klass.quoted_table_name end - def test_set_table_name_with_block - k = Class.new( ActiveRecord::Base ) - assert_deprecated do - k.set_table_name "foo" - k.set_table_name do - ActiveSupport::Deprecation.silence { original_table_name } + "ks" - end - end - assert_equal "fooks", k.table_name - end - def test_set_table_name_with_inheritance k = Class.new( ActiveRecord::Base ) def k.name; "Foo"; end @@ -1450,145 +1434,6 @@ class BasicsTest < ActiveRecord::TestCase assert_equal "foosks", k.table_name end - def test_original_table_name - k = Class.new(ActiveRecord::Base) - def k.name; "Foo"; end - k.table_name = "bar" - - assert_deprecated do - assert_equal "foos", k.original_table_name - end - - k = Class.new(ActiveRecord::Base) - k.table_name = "omg" - k.table_name = "wtf" - - assert_deprecated do - assert_equal "omg", k.original_table_name - end - end - - def test_set_primary_key_with_value - k = Class.new( ActiveRecord::Base ) - k.primary_key = "foo" - assert_equal "foo", k.primary_key - - assert_deprecated do - k.set_primary_key "bar" - end - assert_equal "bar", k.primary_key - end - - def test_set_primary_key_with_block - k = Class.new( ActiveRecord::Base ) - k.primary_key = 'id' - - assert_deprecated do - k.set_primary_key do - "sys_" + ActiveSupport::Deprecation.silence { original_primary_key } - end - end - assert_equal "sys_id", k.primary_key - end - - def test_original_primary_key - k = Class.new(ActiveRecord::Base) - def k.name; "Foo"; end - k.table_name = "posts" - k.primary_key = "bar" - - assert_deprecated do - assert_equal "id", k.original_primary_key - end - - k = Class.new(ActiveRecord::Base) - k.primary_key = "omg" - k.primary_key = "wtf" - - assert_deprecated do - assert_equal "omg", k.original_primary_key - end - end - - def test_set_inheritance_column_with_value - k = Class.new( ActiveRecord::Base ) - k.inheritance_column = "foo" - assert_equal "foo", k.inheritance_column - - assert_deprecated do - k.set_inheritance_column "bar" - end - assert_equal "bar", k.inheritance_column - end - - def test_set_inheritance_column_with_block - k = Class.new( ActiveRecord::Base ) - assert_deprecated do - k.set_inheritance_column do - ActiveSupport::Deprecation.silence { original_inheritance_column } + "_id" - end - end - assert_equal "type_id", k.inheritance_column - end - - def test_original_inheritance_column - k = Class.new(ActiveRecord::Base) - def k.name; "Foo"; end - k.inheritance_column = "omg" - - assert_deprecated do - assert_equal "type", k.original_inheritance_column - end - end - - def test_set_sequence_name_with_value - k = Class.new( ActiveRecord::Base ) - k.sequence_name = "foo" - assert_equal "foo", k.sequence_name - - assert_deprecated do - k.set_sequence_name "bar" - end - assert_equal "bar", k.sequence_name - end - - def test_set_sequence_name_with_block - k = Class.new( ActiveRecord::Base ) - k.table_name = "projects" - orig_name = k.sequence_name - return skip "sequences not supported by db" unless orig_name - - assert_deprecated do - k.set_sequence_name do - ActiveSupport::Deprecation.silence { original_sequence_name } + "_lol" - end - end - assert_equal orig_name + "_lol", k.sequence_name - end - - def test_original_sequence_name - k = Class.new(ActiveRecord::Base) - k.table_name = "projects" - orig_name = k.sequence_name - return skip "sequences not supported by db" unless orig_name - - k = Class.new(ActiveRecord::Base) - k.table_name = "projects" - k.sequence_name = "omg" - - assert_deprecated do - assert_equal orig_name, k.original_sequence_name - end - - k = Class.new(ActiveRecord::Base) - k.table_name = "projects" - k.sequence_name = "omg" - k.sequence_name = "wtf" - assert_deprecated do - assert_equal "omg", k.original_sequence_name - end - end - def test_sequence_name_with_abstract_class ak = Class.new(ActiveRecord::Base) ak.abstract_class = true @@ -1848,7 +1693,7 @@ class BasicsTest < ActiveRecord::TestCase def test_inspect_instance topic = topics(:first) - assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "#{topic.created_at.to_s(:db)}", updated_at: "#{topic.updated_at.to_s(:db)}">), topic.inspect + assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", important: nil, approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "#{topic.created_at.to_s(:db)}", updated_at: "#{topic.updated_at.to_s(:db)}">), topic.inspect end def test_inspect_new_instance diff --git a/activerecord/test/cases/binary_test.rb b/activerecord/test/cases/binary_test.rb index 06c14cb108..f97aade311 100644 --- a/activerecord/test/cases/binary_test.rb +++ b/activerecord/test/cases/binary_test.rb @@ -12,7 +12,7 @@ unless current_adapter?(:SybaseAdapter, :DB2Adapter, :FirebirdAdapter) def test_mixed_encoding str = "\x80" - str.force_encoding('ASCII-8BIT') if str.respond_to?(:force_encoding) + str.force_encoding('ASCII-8BIT') binary = Binary.new :name => 'いただきます!', :data => str binary.save! @@ -23,7 +23,7 @@ unless current_adapter?(:SybaseAdapter, :DB2Adapter, :FirebirdAdapter) # Mysql adapter doesn't properly encode things, so we have to do it if current_adapter?(:MysqlAdapter) - name.force_encoding('UTF-8') if name.respond_to?(:force_encoding) + name.force_encoding('UTF-8') end assert_equal 'いただきます!', name end @@ -33,7 +33,7 @@ unless current_adapter?(:SybaseAdapter, :DB2Adapter, :FirebirdAdapter) FIXTURES.each do |filename| data = File.read(ASSETS_ROOT + "/#{filename}") - data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding) + data.force_encoding('ASCII-8BIT') data.freeze bin = Binary.new(:data => data) diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 5abf3d1af4..66c801ca75 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -458,7 +458,6 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal [ topic.approved ], relation.pluck(:approved) assert_equal [ topic.last_read ], relation.pluck(:last_read) assert_equal [ topic.written_on ], relation.pluck(:written_on) - end def test_pluck_and_uniq @@ -471,4 +470,12 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal [contract.id], company.contracts.pluck(:id) end + def test_pluck_with_serialization + t = Topic.create!(:content => { :foo => :bar }) + assert_equal [{:foo => :bar}], Topic.where(:id => t.id).pluck(:content) + end + + def test_pluck_with_qualified_column_name + assert_equal [1,2,3,4], Topic.order(:id).pluck("topics.id") + end end diff --git a/activerecord/test/cases/configuration_test.rb b/activerecord/test/cases/configuration_test.rb new file mode 100644 index 0000000000..872f1fc33b --- /dev/null +++ b/activerecord/test/cases/configuration_test.rb @@ -0,0 +1,26 @@ +require 'cases/helper' + +class ConfigurationTest < ActiveRecord::TestCase + def test_configuration + @klass = Class.new do + include ActiveRecord::Configuration + end + + ActiveRecord::Configuration.define :omg + + ActiveRecord::Configuration.omg = "omg" + + assert_equal "omg", @klass.new.omg + assert !@klass.new.respond_to?(:omg=) + assert_equal "omg", @klass.omg + + @klass.omg = "wtf" + + assert_equal "wtf", @klass.omg + assert_equal "wtf", @klass.new.omg + ensure + ActiveRecord::Configuration.send(:undef_method, :omg) + ActiveRecord::Configuration::ClassMethods.send(:undef_method, :omg) + ActiveRecord::Configuration::ClassMethods.send(:undef_method, :omg=) + 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 04d543fea9..dc99ac665c 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -8,6 +8,9 @@ module ActiveRecord @handler.establish_connection 'america', Base.connection_pool.spec @klass = Class.new do def self.name; 'america'; end + class << self + alias active_record_super superclass + end end @subklass = Class.new(@klass) do def self.name; 'north america'; end diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb index d4b0f236ee..5f9a742285 100644 --- a/activerecord/test/cases/connection_specification/resolver_test.rb +++ b/activerecord/test/cases/connection_specification/resolver_test.rb @@ -1,7 +1,7 @@ require "cases/helper" module ActiveRecord - class Base + module Core class ConnectionSpecification class ResolverTest < ActiveRecord::TestCase def resolve(spec) diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 99dd74c561..7295d3c6f1 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -213,7 +213,7 @@ class FixturesTest < ActiveRecord::TestCase def test_binary_in_fixtures data = File.open(ASSETS_ROOT + "/flowers.jpg", 'rb') { |f| f.read } - data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding) + data.force_encoding('ASCII-8BIT') data.freeze assert_equal data, @flowers.data end diff --git a/activerecord/test/cases/inclusion_test.rb b/activerecord/test/cases/inclusion_test.rb new file mode 100644 index 0000000000..f2c442c2e1 --- /dev/null +++ b/activerecord/test/cases/inclusion_test.rb @@ -0,0 +1,110 @@ +require 'cases/helper' +require 'models/teapot' + +class BasicInclusionModelTest < ActiveRecord::TestCase + def test_basic_model + Teapot.create!(:name => "Ronnie Kemper") + assert_equal "Ronnie Kemper", Teapot.find(1).name + end + + def test_initialization + t = Teapot.new(:name => "Bob") + assert_equal "Bob", t.name + end + + def test_inherited_model + teapot = CoolTeapot.create!(:name => "Bob") + teapot.reload + + assert_equal "Bob", teapot.name + assert_equal "mmm", teapot.aaahhh + end + + def test_generated_feature_methods + assert Teapot < Teapot::GeneratedFeatureMethods + end + + def test_exists + t = Teapot.create!(:name => "Ronnie Kemper") + assert Teapot.exists?(t) + end + + def test_predicate_builder + t = Teapot.create!(:name => "Bob") + assert_equal "Bob", Teapot.where(:id => [t]).first.name + assert_equal "Bob", Teapot.where(:id => t).first.name + end + + def test_nested_model + assert_equal "ceiling_teapots", Ceiling::Teapot.table_name + end +end + +class InclusionUnitTest < ActiveRecord::TestCase + def setup + @klass = Class.new { include ActiveRecord::Model } + end + + def test_non_abstract_class + assert !@klass.abstract_class? + end + + def test_abstract_class + @klass.abstract_class = true + assert @klass.abstract_class? + end + + def test_establish_connection + assert @klass.respond_to?(:establish_connection) + end + + def test_adapter_connection + assert @klass.respond_to?("#{ActiveRecord::Base.connection_config[:adapter]}_connection") + end + + def test_connection_handler + assert_equal ActiveRecord::Base.connection_handler, @klass.connection_handler + end + + def test_mirrored_configuration + ActiveRecord::Base.time_zone_aware_attributes = true + assert @klass.time_zone_aware_attributes + ActiveRecord::Base.time_zone_aware_attributes = false + assert !@klass.time_zone_aware_attributes + ensure + ActiveRecord::Base.time_zone_aware_attributes = false + end + + # Doesn't really test anything, but this is here to ensure warnings don't occur + def test_included_twice + @klass.send :include, ActiveRecord::Model + end + + def test_deprecation_proxy + assert_equal ActiveRecord::Model.name, ActiveRecord::Model::DeprecationProxy.name + assert_equal ActiveRecord::Base.superclass, assert_deprecated { ActiveRecord::Model::DeprecationProxy.superclass } + + sup, sup2 = nil, nil + ActiveSupport.on_load(:__test_active_record_model_deprecation) do + sup = superclass + sup2 = send(:superclass) + end + assert_deprecated do + ActiveSupport.run_load_hooks(:__test_active_record_model_deprecation, ActiveRecord::Model::DeprecationProxy) + end + assert_equal ActiveRecord::Base.superclass, sup + assert_equal ActiveRecord::Base.superclass, sup2 + end +end + +class InclusionFixturesTest < ActiveRecord::TestCase + fixtures :teapots + + def test_fixtured_record + assert_equal "Bob", teapots(:bob).name + end + + def test_timestamped_fixture + assert_not_nil teapots(:bob).created_at + end +end diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index be7edb858f..f7ee83998d 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -226,48 +226,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase end end -class SetLockingColumnTest < ActiveRecord::TestCase - def test_set_set_locking_column_with_value - k = Class.new( ActiveRecord::Base ) - k.locking_column = "foo" - assert_equal "foo", k.locking_column - - assert_deprecated do - k.set_locking_column "bar" - end - assert_equal "bar", k.locking_column - end - - def test_set_locking_column_with_block - k = Class.new( ActiveRecord::Base ) - k.locking_column = 'foo' - - assert_deprecated do - k.set_locking_column do - "lock_" + ActiveSupport::Deprecation.silence { original_locking_column } - end - end - assert_equal "lock_foo", k.locking_column - end - - def test_original_locking_column - k = Class.new(ActiveRecord::Base) - k.locking_column = "bar" - - assert_deprecated do - assert_equal ActiveRecord::Locking::Optimistic::ClassMethods::DEFAULT_LOCKING_COLUMN, k.original_locking_column - end - - k = Class.new(ActiveRecord::Base) - k.locking_column = "omg" - k.locking_column = "wtf" - - assert_deprecated do - assert_equal "omg", k.original_locking_column - end - end -end - class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase fixtures :people, :legacy_things, :references @@ -421,16 +379,6 @@ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db? assert first.end > second.end end - # Hit by ruby deadlock detection since connection checkout is mutexed. - if RUBY_VERSION < '1.9.0' - def test_second_lock_waits - assert [0.2, 1, 5].any? { |zzz| - first, second = duel(zzz) { Person.find 1, :lock => true } - second.end > first.end - } - end - end - protected def duel(zzz = 5) t0, t1, t2, t3 = nil, nil, nil, nil diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb index 9fff50edcb..8122857f52 100644 --- a/activerecord/test/cases/mass_assignment_security_test.rb +++ b/activerecord/test/cases/mass_assignment_security_test.rb @@ -50,6 +50,13 @@ module MassAssignmentTestHelpers assert_equal 'm', person.gender assert_equal 'rides a sweet bike', person.comments end + + def with_strict_sanitizer + ActiveRecord::Base.mass_assignment_sanitizer = :strict + yield + ensure + ActiveRecord::Base.mass_assignment_sanitizer = :logger + end end module MassAssignmentRelationTestHelpers @@ -323,6 +330,13 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_has_one_build_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.build_best_friend(attributes_hash.except(:id, :comments)) + assert_equal @person.id, best_friend.best_friend_id + end + end + # create def test_has_one_create_with_attr_protected_attributes @@ -350,6 +364,13 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_has_one_create_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.create_best_friend(attributes_hash.except(:id, :comments)) + assert_equal @person.id, best_friend.best_friend_id + end + end + # create! def test_has_one_create_with_bang_with_attr_protected_attributes @@ -377,6 +398,13 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_has_one_create_with_bang_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.create_best_friend!(attributes_hash.except(:id, :comments)) + assert_equal @person.id, best_friend.best_friend_id + end + end + end @@ -438,6 +466,13 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_belongs_to_create_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.create_best_friend_of(attributes_hash.except(:id, :comments)) + assert_equal best_friend.id, @person.best_friend_of_id + end + end + # create! def test_belongs_to_create_with_bang_with_attr_protected_attributes @@ -465,6 +500,13 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_belongs_to_create_with_bang_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.create_best_friend_of!(attributes_hash.except(:id, :comments)) + assert_equal best_friend.id, @person.best_friend_of_id + end + end + end @@ -499,6 +541,13 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_has_many_build_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.best_friends.build(attributes_hash.except(:id, :comments)) + assert_equal @person.id, best_friend.best_friend_id + end + end + # create def test_has_many_create_with_attr_protected_attributes @@ -526,6 +575,13 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_has_many_create_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.best_friends.create(attributes_hash.except(:id, :comments)) + assert_equal @person.id, best_friend.best_friend_id + end + end + # create! def test_has_many_create_with_bang_with_attr_protected_attributes @@ -553,6 +609,13 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_has_many_create_with_bang_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.best_friends.create!(attributes_hash.except(:id, :comments)) + assert_equal @person.id, best_friend.best_friend_id + end + end + end diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index 0eb3d900bd..e17ba76437 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -1,5 +1,4 @@ require "cases/helper" -require 'active_support/core_ext/array/random_access' require 'models/post' require 'models/topic' require 'models/comment' diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 2f28dd4767..2ae9cb4888 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -617,11 +617,6 @@ module NestedAttributesOnACollectionAssociationTests assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.name, @child_2.name] end - def test_should_take_a_hash_with_owner_attributes_and_assign_the_attributes_to_the_associated_model - @pirate.birds.create :name => 'bird', :pirate_attributes => {:id => @pirate.id.to_s, :catchphrase => 'Holla!'} - assert_equal 'Holla!', @pirate.reload.catchphrase - end - def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find #{@child_1.class.name} with ID=1234567890 for Pirate with ID=#{@pirate.id}" do @pirate.attributes = { association_getter => [{ :id => 1234567890 }] } diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb index bc3dfb1078..296621c0d1 100644 --- a/activerecord/test/cases/pooled_connections_test.rb +++ b/activerecord/test/cases/pooled_connections_test.rb @@ -33,14 +33,6 @@ class PooledConnectionsTest < ActiveRecord::TestCase end # Will deadlock due to lack of Monitor timeouts in 1.9 - if RUBY_VERSION < '1.9' - def test_pooled_connection_checkout - checkout_connections - assert_equal 2, @connections.length - assert_equal 2, @timed_out - end - end - def checkout_checkin_connections(pool_size, threads) ActiveRecord::Base.establish_connection(@connection.merge({:pool => pool_size, :wait_timeout => 0.5})) @connection_count = 0 diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index b30db542a7..7fd15027eb 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -36,25 +36,25 @@ class ReflectionTest < ActiveRecord::TestCase def test_read_attribute_names assert_equal( - %w( id title author_name author_email_address bonus_time written_on last_read content group approved replies_count parent_id parent_title type created_at updated_at ).sort, + %w( id title author_name author_email_address bonus_time written_on last_read content important group approved replies_count parent_id parent_title type created_at updated_at ).sort, @first.attribute_names.sort ) end def test_columns - assert_equal 16, Topic.columns.length + assert_equal 17, Topic.columns.length end def test_columns_are_returned_in_the_order_they_were_declared column_names = Topic.columns.map { |column| column.name } - assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content approved replies_count parent_id parent_title type group created_at updated_at), column_names + assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content important approved replies_count parent_id parent_title type group created_at updated_at), column_names end def test_content_columns content_columns = Topic.content_columns content_column_names = content_columns.map {|column| column.name} - assert_equal 12, content_columns.length - assert_equal %w(title author_name author_email_address written_on bonus_time last_read content group approved parent_title created_at updated_at).sort, content_column_names.sort + assert_equal 13, content_columns.length + assert_equal %w(title author_name author_email_address written_on bonus_time last_read content important group approved parent_title created_at updated_at).sort, content_column_names.sort end def test_column_string_type_and_limit diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 5c3a78688e..dd6d7e52d5 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -13,10 +13,8 @@ class SchemaDumperTest < ActiveRecord::TestCase @stream.string end - if "string".encoding_aware? - def test_magic_comment - assert_match "# encoding: #{@stream.external_encoding.name}", standard_dump - end + def test_magic_comment + assert_match "# encoding: #{@stream.external_encoding.name}", standard_dump end def test_schema_dump diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb index 768fbc5b0a..f155b9bc40 100644 --- a/activerecord/test/cases/validations/association_validation_test.rb +++ b/activerecord/test/cases/validations/association_validation_test.rb @@ -91,14 +91,12 @@ class AssociationValidationTest < ActiveRecord::TestCase end def test_validates_size_of_association_utf8 - with_kcode('UTF8') do - assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 } - o = Owner.new('name' => 'あいうえおかきくけこ') - assert !o.save - assert o.errors[:pets].any? - o.pets.build('name' => 'あいうえおかきくけこ') - assert o.valid? - end + assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 } + o = Owner.new('name' => 'あいうえおかきくけこ') + assert !o.save + assert o.errors[:pets].any? + o.pets.build('name' => 'あいうえおかきくけこ') + assert o.valid? end def test_validates_presence_of_belongs_to_association__parent_is_new_record diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index 0f1b3667cc..382ad0a06a 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -149,16 +149,14 @@ class UniquenessValidationTest < ActiveRecord::TestCase assert t2.valid?, "should validate with nil" assert t2.save, "should save with nil" - with_kcode('UTF8') do - t_utf8 = Topic.new("title" => "Я тоже уникальный!") - assert t_utf8.save, "Should save t_utf8 as unique" - - # If database hasn't UTF-8 character set, this test fails - if Topic.find(t_utf8, :select => 'LOWER(title) AS title').title == "я тоже уникальный!" - t2_utf8 = Topic.new("title" => "я тоже УНИКАЛЬНЫЙ!") - assert !t2_utf8.valid?, "Shouldn't be valid" - assert !t2_utf8.save, "Shouldn't save t2_utf8 as unique" - end + t_utf8 = Topic.new("title" => "Я тоже уникальный!") + assert t_utf8.save, "Should save t_utf8 as unique" + + # If database hasn't UTF-8 character set, this test fails + if Topic.find(t_utf8, :select => 'LOWER(title) AS title').title == "я тоже уникальный!" + t2_utf8 = Topic.new("title" => "я тоже УНИКАЛЬНЫЙ!") + assert !t2_utf8.valid?, "Shouldn't be valid" + assert !t2_utf8.save, "Shouldn't save t2_utf8 as unique" end end @@ -256,13 +254,11 @@ class UniquenessValidationTest < ActiveRecord::TestCase end def test_validate_uniqueness_with_limit_and_utf8 - with_kcode('UTF8') do - # Event.title is limited to 5 characters - e1 = Event.create(:title => "一二三四五") - assert e1.valid?, "Could not create an event with a unique, 5 character title" - e2 = Event.create(:title => "一二三四五六七八") - assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique" - end + # Event.title is limited to 5 characters + e1 = Event.create(:title => "一二三四五") + assert e1.valid?, "Could not create an event with a unique, 5 character title" + e2 = Event.create(:title => "一二三四五六七八") + assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique" end def test_validate_straight_inheritance_uniqueness diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb index 5a38f2c6ee..2b4ec81199 100644 --- a/activerecord/test/cases/yaml_serialization_test.rb +++ b/activerecord/test/cases/yaml_serialization_test.rb @@ -5,10 +5,16 @@ class YamlSerializationTest < ActiveRecord::TestCase fixtures :topics def test_to_yaml_with_time_with_zone_should_not_raise_exception + tz = Time.zone Time.zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"] ActiveRecord::Base.time_zone_aware_attributes = true + topic = Topic.new(:written_on => DateTime.now) assert_nothing_raised { topic.to_yaml } + + ensure + Time.zone = tz + ActiveRecord::Base.time_zone_aware_attributes = false end def test_roundtrip diff --git a/activerecord/test/fixtures/teapots.yml b/activerecord/test/fixtures/teapots.yml new file mode 100644 index 0000000000..ff515beb45 --- /dev/null +++ b/activerecord/test/fixtures/teapots.yml @@ -0,0 +1,3 @@ +bob: + id: 1 + name: Bob diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index 967a3625aa..36eb08d02f 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -54,7 +54,7 @@ class LoosePerson < ActiveRecord::Base self.table_name = 'people' self.abstract_class = true - attr_protected :comments + attr_protected :comments, :best_friend_id, :best_friend_of_id attr_protected :as => :admin has_one :best_friend, :class_name => 'LoosePerson', :foreign_key => :best_friend_id @@ -81,4 +81,4 @@ class TightPerson < ActiveRecord::Base accepts_nested_attributes_for :best_friend, :best_friend_of, :best_friends end -class TightDescendant < TightPerson; end
\ No newline at end of file +class TightDescendant < TightPerson; end diff --git a/activerecord/test/models/teapot.rb b/activerecord/test/models/teapot.rb new file mode 100644 index 0000000000..ff18b6a96d --- /dev/null +++ b/activerecord/test/models/teapot.rb @@ -0,0 +1,32 @@ +class Teapot + # I'm a little teapot, + # Short and stout, + # Here is my handle + # Here is my spout + # When I get all steamed up, + # Hear me shout, + # Tip me over and pour me out! + # + # HELL YEAH TEAPOT SONG + + include ActiveRecord::Model +end + +class OMFGIMATEAPOT + def aaahhh + "mmm" + end +end + +class CoolTeapot < OMFGIMATEAPOT + include ActiveRecord::Model + self.table_name = "teapots" +end + +class Ceiling + include ActiveRecord::Model + + class Teapot + include ActiveRecord::Model + end +end diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index ede662450e..1a1a18166a 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -108,6 +108,10 @@ class Topic < ActiveRecord::Base def after_create_for_transaction; end end +class ImportantTopic < Topic + serialize :important, Hash +end + module Web class Topic < ActiveRecord::Base has_many :replies, :dependent => :destroy, :foreign_key => "parent_id", :class_name => 'Web::Reply' diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index bb08f5c181..8706732230 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -596,6 +596,11 @@ ActiveRecord::Schema.define do t.datetime :ending end + create_table :teapots, :force => true do |t| + t.string :name + t.timestamps + end + create_table :topics, :force => true do |t| t.string :title t.string :author_name @@ -607,8 +612,10 @@ ActiveRecord::Schema.define do # Oracle SELECT WHERE clause which causes many unit test failures if current_adapter?(:OracleAdapter) t.string :content, :limit => 4000 + t.string :important, :limit => 4000 else t.text :content + t.text :important end t.boolean :approved, :default => true t.integer :replies_count, :default => 0 diff --git a/activerecord/test/support/connection.rb b/activerecord/test/support/connection.rb index a39794fa39..56369da346 100644 --- a/activerecord/test/support/connection.rb +++ b/activerecord/test/support/connection.rb @@ -1,4 +1,4 @@ -require 'logger' +require 'active_support/logger' require_dependency 'models/course' module ARTest @@ -12,7 +12,7 @@ module ARTest def self.connect puts "Using #{connection_name} with Identity Map #{ActiveRecord::IdentityMap.enabled? ? 'on' : 'off'}" - ActiveRecord::Base.logger = Logger.new("debug.log") + ActiveRecord::Base.logger = ActiveSupport::Logger.new("debug.log") ActiveRecord::Base.configurations = connection_config ActiveRecord::Base.establish_connection 'arunit' Course.establish_connection 'arunit2' diff --git a/activeresource/README.rdoc b/activeresource/README.rdoc index c86289c5fe..8170f29973 100644 --- a/activeresource/README.rdoc +++ b/activeresource/README.rdoc @@ -172,7 +172,9 @@ Destruction of a resource can be invoked as a class and instance method of the r == License -Active Support is released under the MIT license. +Active Support is released under the MIT license: + +* http://www.opensource.org/licenses/MIT == Support diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index f23ee6e045..50c83478d1 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -7,8 +7,6 @@ * Add ActiveSupport::Cache::NullStore for use in development and testing. *Brian Durand* -* Added Enumerable#pluck to wrap the common pattern of collect(&:method) *DHH* - * Module#synchronize is deprecated with no replacement. Please use `monitor` from ruby's standard library. diff --git a/activesupport/README.rdoc b/activesupport/README.rdoc index 1ab8e00608..ed688ecc59 100644 --- a/activesupport/README.rdoc +++ b/activesupport/README.rdoc @@ -19,7 +19,9 @@ Source code can be downloaded as part of the Rails project on GitHub == License -Active Support is released under the MIT license. +Active Support is released under the MIT license: + +* http://www.opensource.org/licenses/MIT == Support diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 896d0e5f65..9fdc7a3b83 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -43,6 +43,7 @@ require "active_support/logger" module ActiveSupport extend ActiveSupport::Autoload + autoload :Concern autoload :DescendantsTracker autoload :FileUpdateChecker autoload :LogSubscriber @@ -56,7 +57,6 @@ module ActiveSupport autoload :Benchmarkable autoload :Cache autoload :Callbacks - autoload :Concern autoload :Configurable autoload :Deprecation autoload :Gzip diff --git a/activesupport/lib/active_support/base64.rb b/activesupport/lib/active_support/base64.rb index 35014cb3d5..b43d2ce9a3 100644 --- a/activesupport/lib/active_support/base64.rb +++ b/activesupport/lib/active_support/base64.rb @@ -1,35 +1,7 @@ -begin - require 'base64' -rescue LoadError -end +require 'base64' module ActiveSupport - if defined? ::Base64 - Base64 = ::Base64 - else - # Base64 provides utility methods for encoding and de-coding binary data - # using a base 64 representation. A base 64 representation of binary data - # consists entirely of printable US-ASCII characters. The Base64 module - # is included in Ruby 1.8, but has been removed in Ruby 1.9. - module Base64 - # Encodes a string to its base 64 representation. Each 60 characters of - # output is separated by a newline character. - # - # ActiveSupport::Base64.encode64("Original unencoded string") - # # => "T3JpZ2luYWwgdW5lbmNvZGVkIHN0cmluZw==\n" - def self.encode64(data) - [data].pack("m") - end - - # Decodes a base 64 encoded string to its original representation. - # - # ActiveSupport::Base64.decode64("T3JpZ2luYWwgdW5lbmNvZGVkIHN0cmluZw==") - # # => "Original unencoded string" - def self.decode64(data) - data.unpack("m").first - end - end - end + Base64 = ::Base64 # Encodes the value as base64 without the newline breaks. This makes the base64 encoding readily usable as URL parameters # or memcache keys without further processing. diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index 530839b24d..e38a8387b4 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -165,7 +165,7 @@ module ActiveSupport # characters properly. def escape_key(key) key = key.to_s.dup - key = key.force_encoding("BINARY") if key.encoding_aware? + key = key.force_encoding("BINARY") key = key.gsub(ESCAPE_KEY_CHARS){ |match| "%#{match.getbyte(0).to_s(16).upcase}" } key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250 key diff --git a/activesupport/lib/active_support/core_ext/array.rb b/activesupport/lib/active_support/core_ext/array.rb index 268c9bed4c..79ba79192a 100644 --- a/activesupport/lib/active_support/core_ext/array.rb +++ b/activesupport/lib/active_support/core_ext/array.rb @@ -4,5 +4,4 @@ require 'active_support/core_ext/array/uniq_by' require 'active_support/core_ext/array/conversions' require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/array/grouping' -require 'active_support/core_ext/array/random_access' require 'active_support/core_ext/array/prepend_and_append' diff --git a/activesupport/lib/active_support/core_ext/array/random_access.rb b/activesupport/lib/active_support/core_ext/array/random_access.rb deleted file mode 100644 index bb1807a68a..0000000000 --- a/activesupport/lib/active_support/core_ext/array/random_access.rb +++ /dev/null @@ -1,30 +0,0 @@ -class Array - # Backport of Array#sample based on Marc-Andre Lafortune's https://github.com/marcandre/backports/ - # Returns a random element or +n+ random elements from the array. - # If the array is empty and +n+ is nil, returns <tt>nil</tt>. - # If +n+ is passed and its value is less than 0, it raises an +ArgumentError+ exception. - # If the value of +n+ is equal or greater than 0 it returns <tt>[]</tt>. - # - # [1,2,3,4,5,6].sample # => 4 - # [1,2,3,4,5,6].sample(3) # => [2, 4, 5] - # [1,2,3,4,5,6].sample(-3) # => ArgumentError: negative array size - # [].sample # => nil - # [].sample(3) # => [] - def sample(n=nil) - return self[Kernel.rand(size)] if n.nil? - n = n.to_int - rescue Exception => e - raise TypeError, "Coercion error: #{n.inspect}.to_int => Integer failed:\n(#{e.message})" - else - raise TypeError, "Coercion error: obj.to_int did NOT return an Integer (was #{n.class})" unless n.kind_of? Integer - raise ArgumentError, "negative array size" if n < 0 - n = size if n > size - result = Array.new(self) - n.times do |i| - r = i + Kernel.rand(size - i) - result[i], result[r] = result[r], result[i] - end - result[n..size] = [] - result - end unless method_defined? :sample -end diff --git a/activesupport/lib/active_support/core_ext/array/uniq_by.rb b/activesupport/lib/active_support/core_ext/array/uniq_by.rb index 9c5f97b0e9..ac3dedc0e3 100644 --- a/activesupport/lib/active_support/core_ext/array/uniq_by.rb +++ b/activesupport/lib/active_support/core_ext/array/uniq_by.rb @@ -1,16 +1,22 @@ class Array - # Returns an unique array based on the criteria given as a +Proc+. + # *DEPRECATED*: Use +Array#uniq+ instead. + # + # Returns a unique array based on the criteria in the block. # # [1, 2, 3, 4].uniq_by { |i| i.odd? } # => [1, 2] # - def uniq_by - hash, array = {}, [] - each { |i| hash[yield(i)] ||= (array << i) } - array + def uniq_by(&block) + ActiveSupport::Deprecation.warn "uniq_by " \ + "is deprecated. Use Array#uniq instead", caller + uniq(&block) end - # Same as uniq_by, but modifies self. - def uniq_by! - replace(uniq_by{ |i| yield(i) }) + # *DEPRECATED*: Use +Array#uniq!+ instead. + # + # Same as +uniq_by+, but modifies +self+. + def uniq_by!(&block) + ActiveSupport::Deprecation.warn "uniq_by! " \ + "is deprecated. Use Array#uniq! instead", caller + uniq!(&block) end end diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index ccd0e9692d..77a5087981 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -26,34 +26,6 @@ module Enumerable end end - # Plucks the value of the passed method for each element and returns the result as an array. Example: - # - # people.pluck(:name) # => [ "David Heinemeier Hansson", "Jamie Heinemeier Hansson" ] - def pluck(method) - collect { |element| element.send(method) } - end - - # Iterates over a collection, passing the current element *and* the - # +memo+ to the block. Handy for building up hashes or - # reducing collections down to one object. Examples: - # - # %w(foo bar).each_with_object({}) { |str, hsh| hsh[str] = str.upcase } - # # => {'foo' => 'FOO', 'bar' => 'BAR'} - # - # *Note* that you can't use immutable objects like numbers, true or false as - # the memo. You would think the following returns 120, but since the memo is - # never changed, it does not. - # - # (1..5).each_with_object(1) { |value, memo| memo *= value } # => 1 - # - def each_with_object(memo) - return to_enum :each_with_object, memo unless block_given? - each do |element| - yield element, memo - end - memo - end unless [].respond_to?(:each_with_object) - # Convert an enumerable to a hash. Examples: # # people.index_by(&:login) diff --git a/activesupport/lib/active_support/core_ext/kernel/singleton_class.rb b/activesupport/lib/active_support/core_ext/kernel/singleton_class.rb index 33612155fb..9bbf1bbd73 100644 --- a/activesupport/lib/active_support/core_ext/kernel/singleton_class.rb +++ b/activesupport/lib/active_support/core_ext/kernel/singleton_class.rb @@ -1,11 +1,4 @@ module Kernel - # Returns the object's singleton class. - def singleton_class - class << self - self - end - end unless respond_to?(:singleton_class) # exists in 1.9.2 - # class_eval on an object acts like singleton_class.class_eval. def class_eval(*args, &block) singleton_class.class_eval(*args, &block) diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb index f399fce410..e672e9dca0 100644 --- a/activesupport/lib/active_support/core_ext/module.rb +++ b/activesupport/lib/active_support/core_ext/module.rb @@ -5,8 +5,7 @@ require 'active_support/core_ext/module/reachable' require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/module/attr_internal' require 'active_support/core_ext/module/delegation' -require 'active_support/core_ext/module/synchronization' require 'active_support/core_ext/module/deprecation' require 'active_support/core_ext/module/remove_method' require 'active_support/core_ext/module/method_names' -require 'active_support/core_ext/module/qualified_const'
\ No newline at end of file +require 'active_support/core_ext/module/qualified_const' diff --git a/activesupport/lib/active_support/core_ext/module/synchronization.rb b/activesupport/lib/active_support/core_ext/module/synchronization.rb deleted file mode 100644 index 061621c0ef..0000000000 --- a/activesupport/lib/active_support/core_ext/module/synchronization.rb +++ /dev/null @@ -1,45 +0,0 @@ -require 'thread' -require 'active_support/core_ext/module/aliasing' -require 'active_support/core_ext/array/extract_options' -require 'active_support/core_ext/module/deprecation' - -class Module - # Synchronize access around a method, delegating synchronization to a - # particular mutex. A mutex (either a Mutex, or any object that responds to - # #synchronize and yields to a block) must be provided as a final :with option. - # The :with option should be a symbol or string, and can represent a method, - # constant, or instance or class variable. - # Example: - # class SharedCache - # @@lock = Mutex.new - # def expire - # ... - # end - # synchronize :expire, :with => :@@lock - # end - def synchronize(*methods) - options = methods.extract_options! - unless options.is_a?(Hash) && with = options[:with] - raise ArgumentError, "Synchronization needs a mutex. Supply an options hash with a :with key as the last argument (e.g. synchronize :hello, :with => :@mutex)." - end - - methods.each do |method| - aliased_method, punctuation = method.to_s.sub(/([?!=])$/, ''), $1 - - if method_defined?("#{aliased_method}_without_synchronization#{punctuation}") - raise ArgumentError, "#{method} is already synchronized. Double synchronization is not currently supported." - end - - module_eval(<<-EOS, __FILE__, __LINE__ + 1) - def #{aliased_method}_with_synchronization#{punctuation}(*args, &block) # def expire_with_synchronization(*args, &block) - #{with}.synchronize do # @@lock.synchronize do - #{aliased_method}_without_synchronization#{punctuation}(*args, &block) # expire_without_synchronization(*args, &block) - end # end - end # end - EOS - - alias_method_chain method, :synchronization - end - end - deprecate :synchronize -end diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index fe27f45295..7271671908 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -89,9 +89,6 @@ class Hash end class String - # 0x3000: fullwidth whitespace - NON_WHITESPACE_REGEXP = %r![^\s#{[0x3000].pack("U")}]! - # A string is blank if it's empty or contains whitespaces only: # # "".blank? # => true @@ -100,12 +97,7 @@ class String # " something here ".blank? # => false # def blank? - # 1.8 does not takes [:space:] properly - if encoding_aware? - self !~ /[^[:space:]]/ - else - self !~ NON_WHITESPACE_REGEXP - end + self !~ /[^[:space:]]/ end end diff --git a/activesupport/lib/active_support/core_ext/process.rb b/activesupport/lib/active_support/core_ext/process.rb deleted file mode 100644 index 0b0bc6dc69..0000000000 --- a/activesupport/lib/active_support/core_ext/process.rb +++ /dev/null @@ -1 +0,0 @@ -require 'active_support/core_ext/process/daemon' diff --git a/activesupport/lib/active_support/core_ext/process/daemon.rb b/activesupport/lib/active_support/core_ext/process/daemon.rb deleted file mode 100644 index f5202ddee4..0000000000 --- a/activesupport/lib/active_support/core_ext/process/daemon.rb +++ /dev/null @@ -1,23 +0,0 @@ -module Process - def self.daemon(nochdir = nil, noclose = nil) - exit if fork # Parent exits, child continues. - Process.setsid # Become session leader. - exit if fork # Zap session leader. See [1]. - - unless nochdir - Dir.chdir "/" # Release old working directory. - end - - File.umask 0000 # Ensure sensible umask. Adjust as needed. - - unless noclose - STDIN.reopen "/dev/null" # Free file descriptors and - STDOUT.reopen "/dev/null", "a" # point them somewhere sensible. - STDERR.reopen '/dev/null', 'a' - end - - trap("TERM") { exit } - - return 0 - end unless respond_to?(:daemon) -end diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb index c0d5cdf2d5..9b5266c58c 100644 --- a/activesupport/lib/active_support/core_ext/string/access.rb +++ b/activesupport/lib/active_support/core_ext/string/access.rb @@ -1,99 +1,35 @@ require "active_support/multibyte" class String - unless '1.9'.respond_to?(:force_encoding) - # Returns the character at the +position+ treating the string as an array (where 0 is the first character). - # - # Examples: - # "hello".at(0) # => "h" - # "hello".at(4) # => "o" - # "hello".at(10) # => ERROR if < 1.9, nil in 1.9 - def at(position) - mb_chars[position, 1].to_s - end - - # Returns the remaining of the string from the +position+ treating the string as an array (where 0 is the first character). - # - # Examples: - # "hello".from(0) # => "hello" - # "hello".from(2) # => "llo" - # "hello".from(10) # => "" if < 1.9, nil in 1.9 - def from(position) - mb_chars[position..-1].to_s - end - - # Returns the beginning of the string up to the +position+ treating the string as an array (where 0 is the first character). - # - # Examples: - # "hello".to(0) # => "h" - # "hello".to(2) # => "hel" - # "hello".to(10) # => "hello" - def to(position) - mb_chars[0..position].to_s - end - - # Returns the first character of the string or the first +limit+ characters. - # - # Examples: - # "hello".first # => "h" - # "hello".first(2) # => "he" - # "hello".first(10) # => "hello" - def first(limit = 1) - if limit == 0 - '' - elsif limit >= size - self - else - mb_chars[0...limit].to_s - end - end - - # Returns the last character of the string or the last +limit+ characters. - # - # Examples: - # "hello".last # => "o" - # "hello".last(2) # => "lo" - # "hello".last(10) # => "hello" - def last(limit = 1) - if limit == 0 - '' - elsif limit >= size - self - else - mb_chars[(-limit)..-1].to_s - end - end - else - def at(position) - self[position] - end + def at(position) + self[position] + end - def from(position) - self[position..-1] - end + def from(position) + self[position..-1] + end - def to(position) - self[0..position] - end + def to(position) + self[0..position] + end - def first(limit = 1) - if limit == 0 - '' - elsif limit >= size - self - else - to(limit - 1) - end + def first(limit = 1) + if limit == 0 + '' + elsif limit >= size + self + else + to(limit - 1) end + end - def last(limit = 1) - if limit == 0 - '' - elsif limit >= size - self - else - from(-limit) - end + def last(limit = 1) + if limit == 0 + '' + elsif limit >= size + self + else + from(-limit) end end end diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb index 0f8933b658..541f969faa 100644 --- a/activesupport/lib/active_support/core_ext/string/conversions.rb +++ b/activesupport/lib/active_support/core_ext/string/conversions.rb @@ -1,37 +1,7 @@ -# encoding: utf-8 require 'date' -require 'active_support/core_ext/time/publicize_conversion_methods' require 'active_support/core_ext/time/calculations' class String - # Returns the codepoint of the first character of the string, assuming a - # single-byte character encoding: - # - # "a".ord # => 97 - # "à".ord # => 224, in ISO-8859-1 - # - # This method is defined in Ruby 1.8 for Ruby 1.9 forward compatibility on - # these character encodings. - # - # <tt>ActiveSupport::Multibyte::Chars#ord</tt> is forward compatible with - # Ruby 1.9 on UTF8 strings: - # - # "a".mb_chars.ord # => 97 - # "à".mb_chars.ord # => 224, in UTF8 - # - # Note that the 224 is different in both examples. In ISO-8859-1 "à" is - # represented as a single byte, 224. In UTF8 it is represented with two - # bytes, namely 195 and 160, but its Unicode codepoint is 224. If we - # call +ord+ on the UTF8 string "à" the return value will be 195. That is - # not an error, because UTF8 is unsupported, the call itself would be - # bogus. - def ord - self[0] - end unless method_defined?(:ord) - - # +getbyte+ backport from Ruby 1.9 - alias_method :getbyte, :[] unless method_defined?(:getbyte) - # Form can be either :utc (default) or :local. def to_time(form = :utc) return nil if self.blank? diff --git a/activesupport/lib/active_support/core_ext/string/encoding.rb b/activesupport/lib/active_support/core_ext/string/encoding.rb index 236f72e933..dc635ed6a5 100644 --- a/activesupport/lib/active_support/core_ext/string/encoding.rb +++ b/activesupport/lib/active_support/core_ext/string/encoding.rb @@ -1,5 +1,8 @@ +require 'active_support/deprecation' + class String def encoding_aware? + ActiveSupport::Deprecation.warn 'String#encoding_aware? is deprecated', caller true end -end
\ No newline at end of file +end diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index c6d861d124..6cb2ea68b3 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -6,33 +6,21 @@ class ERB HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"' } JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003E', '<' => '\u003C' } - # Detect whether 1.9 can transcode with XML escaping. - if '"><&""' == ('><&"'.encode('utf-8', :xml => :attr) rescue false) - # A utility method for escaping HTML tag characters. - # This method is also aliased as <tt>h</tt>. - # - # In your ERB templates, use this method to escape any unsafe content. For example: - # <%=h @person.name %> - # - # ==== Example: - # puts html_escape("is a > 0 & a < 10?") - # # => is a > 0 & a < 10? - def html_escape(s) - s = s.to_s - if s.html_safe? - s - else - s.encode(s.encoding, :xml => :attr)[1...-1].html_safe - end - end - else - def html_escape(s) #:nodoc: - s = s.to_s - if s.html_safe? - s - else - s.gsub(/[&"><]/n) { |special| HTML_ESCAPE[special] }.html_safe - end + # A utility method for escaping HTML tag characters. + # This method is also aliased as <tt>h</tt>. + # + # In your ERB templates, use this method to escape any unsafe content. For example: + # <%=h @person.name %> + # + # ==== Example: + # puts html_escape("is a > 0 & a < 10?") + # # => is a > 0 & a < 10? + def html_escape(s) + s = s.to_s + if s.html_safe? + s + else + s.encode(s.encoding, :xml => :attr)[1...-1].html_safe end end diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb index 49ac18d245..5a17fc1afa 100644 --- a/activesupport/lib/active_support/core_ext/time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/time/conversions.rb @@ -1,5 +1,4 @@ require 'active_support/inflector/methods' -require 'active_support/core_ext/time/publicize_conversion_methods' require 'active_support/values/time_zone' class Time diff --git a/activesupport/lib/active_support/core_ext/time/marshal.rb b/activesupport/lib/active_support/core_ext/time/marshal.rb index 457d3f5b62..1bf622d6a6 100644 --- a/activesupport/lib/active_support/core_ext/time/marshal.rb +++ b/activesupport/lib/active_support/core_ext/time/marshal.rb @@ -1,30 +1,3 @@ -# Pre-1.9 versions of Ruby have a bug with marshaling Time instances, where utc instances are -# unmarshalled in the local zone, instead of utc. We're layering behavior on the _dump and _load -# methods so that utc instances can be flagged on dump, and coerced back to utc on load. -if !Marshal.load(Marshal.dump(Time.now.utc)).utc? - class Time - class << self - alias_method :_load_without_utc_flag, :_load - def _load(marshaled_time) - time = _load_without_utc_flag(marshaled_time) - time.instance_eval do - if defined?(@marshal_with_utc_coercion) - val = remove_instance_variable("@marshal_with_utc_coercion") - end - val ? utc : self - end - end - end - - alias_method :_dump_without_utc_flag, :_dump - def _dump(*args) - obj = dup - obj.instance_variable_set('@marshal_with_utc_coercion', utc?) - obj._dump_without_utc_flag(*args) - end - end -end - # Ruby 1.9.2 adds utc_offset and zone to Time, but marshaling only # preserves utc_offset. Preserve zone also, even though it may not # work in some edge cases. diff --git a/activesupport/lib/active_support/core_ext/time/publicize_conversion_methods.rb b/activesupport/lib/active_support/core_ext/time/publicize_conversion_methods.rb deleted file mode 100644 index e1878d3c20..0000000000 --- a/activesupport/lib/active_support/core_ext/time/publicize_conversion_methods.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'date' - -class Time - # Ruby 1.8-cvs and early 1.9 series define private Time#to_date - %w(to_date to_datetime).each do |method| - if private_instance_methods.include?(method) || private_instance_methods.include?(method.to_sym) - public method - end - end -end diff --git a/activesupport/lib/active_support/gzip.rb b/activesupport/lib/active_support/gzip.rb index 9651f02c73..f7036315d6 100644 --- a/activesupport/lib/active_support/gzip.rb +++ b/activesupport/lib/active_support/gzip.rb @@ -8,7 +8,7 @@ module ActiveSupport class Stream < StringIO def initialize(*) super - set_encoding "BINARY" if "".encoding_aware? + set_encoding "BINARY" end def close; rewind; end end diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index 925fa2a2c7..d7181035d3 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -119,9 +119,7 @@ module ActiveSupport end def escape(string) - if string.respond_to?(:force_encoding) - string = string.encode(::Encoding::UTF_8, :undef => :replace).force_encoding(::Encoding::BINARY) - end + string = string.encode(::Encoding::UTF_8, :undef => :replace).force_encoding(::Encoding::BINARY) json = string. gsub(escape_regex) { |s| ESCAPED_CHARS[s] }. gsub(/([\xC0-\xDF][\x80-\xBF]| @@ -130,7 +128,7 @@ module ActiveSupport s.unpack("U*").pack("n*").unpack("H*")[0].gsub(/.{4}/n, '\\\\u\&') } json = %("#{json}") - json.force_encoding(::Encoding::UTF_8) if json.respond_to?(:force_encoding) + json.force_encoding(::Encoding::UTF_8) json end end @@ -281,4 +279,4 @@ class DateTime strftime('%Y/%m/%d %H:%M:%S %z') end end -end
\ No newline at end of file +end diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index ba35b515f2..dcc176e93f 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -282,9 +282,7 @@ module ActiveSupport #:nodoc: return nil if byte_offset.nil? return 0 if @wrapped_string == '' - if @wrapped_string.respond_to?(:force_encoding) - @wrapped_string = @wrapped_string.dup.force_encoding(Encoding::ASCII_8BIT) - end + @wrapped_string = @wrapped_string.dup.force_encoding(Encoding::ASCII_8BIT) begin @wrapped_string[0...byte_offset].unpack('U*').length diff --git a/activesupport/lib/active_support/multibyte/utils.rb b/activesupport/lib/active_support/multibyte/utils.rb index 94b393cee2..bd6d4bad41 100644 --- a/activesupport/lib/active_support/multibyte/utils.rb +++ b/activesupport/lib/active_support/multibyte/utils.rb @@ -2,36 +2,14 @@ module ActiveSupport #:nodoc: module Multibyte #:nodoc: - if Kernel.const_defined?(:Encoding) - # Returns a regular expression that matches valid characters in the current encoding - def self.valid_character - VALID_CHARACTER[Encoding.default_external.to_s] - end - else - def self.valid_character - case $KCODE - when 'UTF8' - VALID_CHARACTER['UTF-8'] - when 'SJIS' - VALID_CHARACTER['Shift_JIS'] - end - end + # Returns a regular expression that matches valid characters in the current encoding + def self.valid_character + VALID_CHARACTER[Encoding.default_external.to_s] end - if 'string'.respond_to?(:valid_encoding?) - # Verifies the encoding of a string - def self.verify(string) - string.valid_encoding? - end - else - def self.verify(string) - if expression = valid_character - # Splits the string on character boundaries, which are determined based on $KCODE. - string.split(//).all? { |c| expression =~ c } - else - true - end - end + # Verifies the encoding of a string + def self.verify(string) + string.valid_encoding? end # Verifies the encoding of the string and raises an exception when it's not valid @@ -39,22 +17,11 @@ module ActiveSupport #:nodoc: raise EncodingError.new("Found characters with invalid encoding") unless verify(string) end - if 'string'.respond_to?(:force_encoding) - # Removes all invalid characters from the string. - # - # Note: this method is a no-op in Ruby 1.9 - def self.clean(string) - string - end - else - def self.clean(string) - if expression = valid_character - # Splits the string on character boundaries, which are determined based on $KCODE. - string.split(//).grep(expression).join - else - string - end - end + # Removes all invalid characters from the string. + # + # Note: this method is a no-op in Ruby 1.9 + def self.clean(string) + string end end end diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb index 1638512af0..f696716cc8 100644 --- a/activesupport/lib/active_support/railtie.rb +++ b/activesupport/lib/active_support/railtie.rb @@ -5,12 +5,6 @@ module ActiveSupport class Railtie < Rails::Railtie config.active_support = ActiveSupport::OrderedOptions.new - # Loads support for "whiny nil" (noisy warnings when methods are invoked - # on +nil+ values) if Configuration#whiny_nils is true. - initializer "active_support.initialize_whiny_nils" do |app| - require 'active_support/whiny_nil' if app.config.whiny_nils - end - initializer "active_support.deprecation_behavior" do |app| if deprecation = app.config.active_support.deprecation ActiveSupport::Deprecation.behavior = deprecation diff --git a/activesupport/lib/active_support/ruby/shim.rb b/activesupport/lib/active_support/ruby/shim.rb index 608b3fe4b9..fd59677d83 100644 --- a/activesupport/lib/active_support/ruby/shim.rb +++ b/activesupport/lib/active_support/ruby/shim.rb @@ -3,8 +3,7 @@ # # Date next_year, next_month # DateTime to_date, to_datetime, xmlschema -# Enumerable group_by, each_with_object, none? -# Process Process.daemon +# Enumerable group_by, none? # REXML security fix # String ord # Time to_date, to_time, to_datetime @@ -12,11 +11,10 @@ require 'active_support' require 'active_support/core_ext/date/calculations' require 'active_support/core_ext/date_time/conversions' require 'active_support/core_ext/enumerable' -require 'active_support/core_ext/process/daemon' require 'active_support/core_ext/string/conversions' require 'active_support/core_ext/string/interpolation' require 'active_support/core_ext/string/encoding' require 'active_support/core_ext/rexml' require 'active_support/core_ext/time/conversions' require 'active_support/core_ext/file/path' -require 'active_support/core_ext/module/method_names'
\ No newline at end of file +require 'active_support/core_ext/module/method_names' diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb index 0f3ac0c132..8eae43188d 100644 --- a/activesupport/lib/active_support/tagged_logging.rb +++ b/activesupport/lib/active_support/tagged_logging.rb @@ -32,9 +32,9 @@ module ActiveSupport %w( fatal error warn info debug unknown ).each do |severity| eval <<-EOM, nil, __FILE__, __LINE__ + 1 - def #{severity}(progname = nil, &block) - add(Logger::#{severity.upcase}, progname, &block) - end + def #{severity}(progname = nil, &block) # def warn(progname = nil, &block) + add(Logger::#{severity.upcase}, progname, &block) # add(Logger::WARN, progname, &block) + end # end EOM end diff --git a/activesupport/lib/active_support/time.rb b/activesupport/lib/active_support/time.rb index 4ef289a414..9634b52ecf 100644 --- a/activesupport/lib/active_support/time.rb +++ b/activesupport/lib/active_support/time.rb @@ -13,7 +13,6 @@ end require 'date' require 'time' -require 'active_support/core_ext/time/publicize_conversion_methods' require 'active_support/core_ext/time/marshal' require 'active_support/core_ext/time/acts_like' require 'active_support/core_ext/time/calculations' diff --git a/activesupport/lib/active_support/whiny_nil.rb b/activesupport/lib/active_support/whiny_nil.rb deleted file mode 100644 index a065233679..0000000000 --- a/activesupport/lib/active_support/whiny_nil.rb +++ /dev/null @@ -1,22 +0,0 @@ -# Extensions to +nil+ which allow for more helpful error messages for people who -# are new to Rails. -# -# NilClass#id exists in Ruby 1.8 (though it is deprecated). Since +id+ is a fundamental -# method of Active Record models NilClass#id is redefined as well to raise a RuntimeError -# and warn the user. She probably wanted a model database identifier and the 4 -# returned by the original method could result in obscure bugs. -# -# The flag <tt>config.whiny_nils</tt> determines whether this feature is enabled. -# By default it is on in development and test modes, and it is off in production -# mode. -class NilClass - def self.add_whiner(klass) - ActiveSupport::Deprecation.warn "NilClass.add_whiner is deprecated and this functionality is " \ - "removed from Rails versions as it affects Ruby 1.9 performance.", caller - end - - # Raises a RuntimeError when you attempt to call +id+ on +nil+. - def id - raise RuntimeError, "Called id for nil, which would mistakenly be #{object_id} -- if you really wanted the id of nil, use object_id", caller - end -end diff --git a/activesupport/test/buffered_logger_test.rb b/activesupport/test/buffered_logger_test.rb index 1540e8df9b..b9891c74c8 100644 --- a/activesupport/test/buffered_logger_test.rb +++ b/activesupport/test/buffered_logger_test.rb @@ -32,9 +32,7 @@ class BufferedLoggerTest < Test::Unit::TestCase logger.level = Logger::DEBUG str = "\x80" - if str.respond_to?(:force_encoding) - str.force_encoding("ASCII-8BIT") - end + str.force_encoding("ASCII-8BIT") logger.add Logger::DEBUG, str ensure @@ -52,9 +50,7 @@ class BufferedLoggerTest < Test::Unit::TestCase logger.level = Logger::DEBUG str = "\x80" - if str.respond_to?(:force_encoding) - str.force_encoding("ASCII-8BIT") - end + str.force_encoding("ASCII-8BIT") logger.add Logger::DEBUG, str ensure @@ -124,9 +120,7 @@ class BufferedLoggerTest < Test::Unit::TestCase @logger.info(BYTE_STRING) assert @output.string.include?(UNICODE_STRING) byte_string = @output.string.dup - if byte_string.respond_to?(:force_encoding) - byte_string.force_encoding("ASCII-8BIT") - end + byte_string.force_encoding("ASCII-8BIT") assert byte_string.include?(BYTE_STRING) end end diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index 5488070d8c..aa6fb14e7b 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -398,22 +398,9 @@ end # The error is caused by charcter encodings that can't be compared with ASCII-8BIT regular expressions and by special # characters like the umlaut in UTF-8. module EncodedKeyCacheBehavior - if defined?(Encoding) - Encoding.list.each do |encoding| - define_method "test_#{encoding.name.underscore}_encoded_values" do - key = "foo".force_encoding(encoding) - assert @cache.write(key, "1", :raw => true) - assert_equal "1", @cache.read(key) - assert_equal "1", @cache.fetch(key) - assert @cache.delete(key) - assert_equal "2", @cache.fetch(key, :raw => true) { "2" } - assert_equal 3, @cache.increment(key) - assert_equal 2, @cache.decrement(key) - end - end - - def test_common_utf8_values - key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8) + Encoding.list.each do |encoding| + define_method "test_#{encoding.name.underscore}_encoded_values" do + key = "foo".force_encoding(encoding) assert @cache.write(key, "1", :raw => true) assert_equal "1", @cache.read(key) assert_equal "1", @cache.fetch(key) @@ -422,12 +409,23 @@ module EncodedKeyCacheBehavior assert_equal 3, @cache.increment(key) assert_equal 2, @cache.decrement(key) end + end - def test_retains_encoding - key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8) - assert @cache.write(key, "1", :raw => true) - assert_equal Encoding::UTF_8, key.encoding - end + def test_common_utf8_values + key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8) + assert @cache.write(key, "1", :raw => true) + assert_equal "1", @cache.read(key) + assert_equal "1", @cache.fetch(key) + assert @cache.delete(key) + assert_equal "2", @cache.fetch(key, :raw => true) { "2" } + assert_equal 3, @cache.increment(key) + assert_equal 2, @cache.decrement(key) + end + + def test_retains_encoding + key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8) + assert @cache.write(key, "1", :raw => true) + assert_equal Encoding::UTF_8, key.encoding end end diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb index 52231aaeb0..278734027a 100644 --- a/activesupport/test/core_ext/array_ext_test.rb +++ b/activesupport/test/core_ext/array_ext_test.rb @@ -343,54 +343,34 @@ end class ArrayUniqByTests < Test::Unit::TestCase def test_uniq_by - assert_equal [1,2], [1,2,3,4].uniq_by { |i| i.odd? } - assert_equal [1,2], [1,2,3,4].uniq_by(&:even?) - assert_equal((-5..0).to_a, (-5..5).to_a.uniq_by{ |i| i**2 }) + ActiveSupport::Deprecation.silence do + assert_equal [1,2], [1,2,3,4].uniq_by { |i| i.odd? } + assert_equal [1,2], [1,2,3,4].uniq_by(&:even?) + assert_equal((-5..0).to_a, (-5..5).to_a.uniq_by{ |i| i**2 }) + end end def test_uniq_by! a = [1,2,3,4] - a.uniq_by! { |i| i.odd? } + ActiveSupport::Deprecation.silence do + a.uniq_by! { |i| i.odd? } + end assert_equal [1,2], a a = [1,2,3,4] - a.uniq_by! { |i| i.even? } + ActiveSupport::Deprecation.silence do + a.uniq_by! { |i| i.even? } + end assert_equal [1,2], a a = (-5..5).to_a - a.uniq_by! { |i| i**2 } + ActiveSupport::Deprecation.silence do + a.uniq_by! { |i| i**2 } + end assert_equal((-5..0).to_a, a) end end -class ArrayExtRandomTests < ActiveSupport::TestCase - def test_sample_from_array - assert_nil [].sample - assert_equal [], [].sample(5) - assert_equal 42, [42].sample - assert_equal [42], [42].sample(5) - - a = [:foo, :bar, 42] - s = a.sample(2) - assert_equal 2, s.size - assert_equal 1, (a-s).size - assert_equal [], a-(0..20).sum{a.sample(2)} - - o = Object.new - def o.to_int; 1; end - assert_equal [0], [0].sample(o) - - o = Object.new - assert_raises(TypeError) { [0].sample(o) } - - o = Object.new - def o.to_int; ''; end - assert_raises(TypeError) { [0].sample(o) } - - assert_raises(ArgumentError) { [0].sample(-7) } - end -end - class ArrayWrapperTests < Test::Unit::TestCase class FakeCollection def to_ary diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index 7ce7c4d52d..f10e6c82e4 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -86,15 +86,6 @@ class EnumerableTests < Test::Unit::TestCase assert_equal 'abc', ('a'..'c').sum end - def test_each_with_object - enum = GenericEnumerable.new(%w(foo bar)) - result = enum.each_with_object({}) { |str, hsh| hsh[str] = str.upcase } - assert_equal({'foo' => 'FOO', 'bar' => 'BAR'}, result) - assert_equal Enumerator, enum.each_with_object({}).class - result2 = enum.each_with_object({}).each{|str, hsh| hsh[str] = str.upcase} - assert_equal result, result2 - end - def test_index_by payments = GenericEnumerable.new([ Payment.new(5), Payment.new(15), Payment.new(10) ]) assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) }, @@ -126,11 +117,4 @@ class EnumerableTests < Test::Unit::TestCase assert_equal true, GenericEnumerable.new([ 1 ]).exclude?(2) assert_equal false, GenericEnumerable.new([ 1 ]).exclude?(1) end - - def test_pluck_single_method - person = Struct.new(:name) - people = [ person.new("David"), person.new("Jamie") ] - - assert_equal [ "David", "Jamie" ], people.pluck(:name) - end -end
\ No newline at end of file +end diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb index 995bc0751a..73a7179872 100644 --- a/activesupport/test/core_ext/kernel_test.rb +++ b/activesupport/test/core_ext/kernel_test.rb @@ -42,11 +42,6 @@ class KernelTest < Test::Unit::TestCase assert_equal 1, silence_stderr { 1 } end - def test_singleton_class - o = Object.new - assert_equal class << o; self end, o.singleton_class - end - def test_class_eval o = Object.new class << o; @x = 1; end @@ -112,4 +107,4 @@ class KernelDebuggerTest < Test::Unit::TestCase ensure Object.send(:remove_const, "Rails") end -end
\ No newline at end of file +end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index aeae2579b4..2e44cbe247 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -445,7 +445,9 @@ class OutputSafetyTest < ActiveSupport::TestCase end test 'knows whether it is encoding aware' do - assert 'ruby'.encoding_aware? + assert_deprecated do + assert 'ruby'.encoding_aware? + end end test "call to_param returns a normal string" do diff --git a/activesupport/test/gzip_test.rb b/activesupport/test/gzip_test.rb index f564e63f29..626981dc9d 100644 --- a/activesupport/test/gzip_test.rb +++ b/activesupport/test/gzip_test.rb @@ -9,10 +9,7 @@ class GzipTest < Test::Unit::TestCase def test_compress_should_return_a_binary_string compressed = ActiveSupport::Gzip.compress('') - if "".encoding_aware? - assert_equal Encoding.find('binary'), compressed.encoding - end - + assert_equal Encoding.find('binary'), compressed.encoding assert !compressed.blank?, "a compressed blank string should not be blank" end end diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index ad9a497515..a6435a763a 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -91,11 +91,11 @@ class TestJSONEncoding < Test::Unit::TestCase def test_utf8_string_encoded_properly result = ActiveSupport::JSON.encode('€2.99') assert_equal '"\\u20ac2.99"', result - assert_equal(Encoding::UTF_8, result.encoding) if result.respond_to?(:encoding) + assert_equal(Encoding::UTF_8, result.encoding) result = ActiveSupport::JSON.encode('✎☺') assert_equal '"\\u270e\\u263a"', result - assert_equal(Encoding::UTF_8, result.encoding) if result.respond_to?(:encoding) + assert_equal(Encoding::UTF_8, result.encoding) end def test_non_utf8_string_transcodes diff --git a/activesupport/test/multibyte_test_helpers.rb b/activesupport/test/multibyte_test_helpers.rb index 8839b75601..fdbe2f4350 100644 --- a/activesupport/test/multibyte_test_helpers.rb +++ b/activesupport/test/multibyte_test_helpers.rb @@ -3,10 +3,7 @@ module MultibyteTestHelpers UNICODE_STRING = 'こにちわ' ASCII_STRING = 'ohayo' - BYTE_STRING = "\270\236\010\210\245" - if BYTE_STRING.respond_to?(:force_encoding) - BYTE_STRING.force_encoding("ASCII-8BIT") - end + BYTE_STRING = "\270\236\010\210\245".force_encoding("ASCII-8BIT") def chars(str) ActiveSupport::Multibyte::Chars.new(str) diff --git a/activesupport/test/whiny_nil_test.rb b/activesupport/test/whiny_nil_test.rb deleted file mode 100644 index 8c1f1b2677..0000000000 --- a/activesupport/test/whiny_nil_test.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'abstract_unit' -require 'active_support/whiny_nil' - -class WhinyNilTest < Test::Unit::TestCase - def test_id - nil.id - rescue RuntimeError => nme - assert_no_match(/nil:NilClass/, nme.message) - assert_match(Regexp.new(nil.object_id.to_s), nme.message) - end -end diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 0bd76df6d7..9b9be5eea4 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,5 +1,9 @@ ## Rails 3.2.0 (unreleased) ## +* Guides are available as a single .mobi for the Kindle and free Kindle readers apps. *Michael Pearson & Xavier Noria* + +* Allow scaffold/model/migration generators to accept a "index" and "uniq" modifiers, as in: "tracking_id:integer:uniq" in order to generate (unique) indexes. Some types also accept custom options, for instance, you can specify the precision and scale for decimals as "price:decimal{7,2}". *Dmitrii Samoilov* + * Added `config.exceptions_app` to set the exceptions application invoked by the ShowException middleware when an exception happens. Defaults to `ActionDispatch::PublicExceptions.new(Rails.public_path)`. *José Valim* * Speed up development by only reloading classes if dependencies files changed. This can be turned off by setting `config.reload_classes_only_on_change` to false. *José Valim* @@ -32,19 +36,6 @@ * Remove old 'config.paths.app.controller' API in favor of 'config.paths["app/controller"]' API *Guillermo Iguaran* -* Rails 3.1.1 - -* Add jquery-rails to Gemfile of plugins, test/dummy app needs it. Closes #3091. *Santiago Pastorino* - -* Add config.assets.initialize_on_precompile which, when set to false, forces - `rake assets:precompile` to load the application but does not initialize it. - - To the app developer, this means configuration add in - config/initializers/* will not be executed. - - Plugins developers need to special case their initializers that are - meant to be run in the assets group by adding :group => :assets. - ## Rails 3.1.2 (unreleased) ## * Engines: don't blow up if db/seeds.rb is missing. @@ -55,6 +46,19 @@ *GH 2564* *José Valim* + +## Rails 3.1.1 ## + +* Add jquery-rails to Gemfile of plugins, test/dummy app needs it. Closes #3091. *Santiago Pastorino* + +* Add config.assets.initialize_on_precompile which, when set to false, forces + `rake assets:precompile` to load the application but does not initialize it. + + To the app developer, this means configuration add in + config/initializers/* will not be executed. + + Plugins developers need to special case their initializers that are + meant to be run in the assets group by adding :group => :assets. ## Rails 3.1.0 (August 30, 2011) ## diff --git a/railties/README.rdoc b/railties/README.rdoc index ae40600401..6248b5feed 100644 --- a/railties/README.rdoc +++ b/railties/README.rdoc @@ -21,7 +21,9 @@ Source code can be downloaded as part of the Rails project on GitHub == License -Railties is released under the MIT license. +Railties is released under the MIT license: + +* http://www.opensource.org/licenses/MIT == Support diff --git a/railties/guides/assets/images/rails_guides_kindle_cover.jpg b/railties/guides/assets/images/rails_guides_kindle_cover.jpg Binary files differnew file mode 100644 index 0000000000..9eb16720a9 --- /dev/null +++ b/railties/guides/assets/images/rails_guides_kindle_cover.jpg diff --git a/railties/guides/assets/stylesheets/kindle.css b/railties/guides/assets/stylesheets/kindle.css new file mode 100644 index 0000000000..b26cd1786a --- /dev/null +++ b/railties/guides/assets/stylesheets/kindle.css @@ -0,0 +1,11 @@ +p { text-indent: 0; } + +p, H1, H2, H3, H4, H5, H6, H7, H8, table { margin-top: 1em;} + +.pagebreak { page-break-before: always; } +#toc H3 { + text-indent: 1em; +} +#toc .document { + text-indent: 2em; +}
\ No newline at end of file diff --git a/railties/guides/assets/stylesheets/main.css b/railties/guides/assets/stylesheets/main.css index cf6e9e5cc8..4a775f632c 100644 --- a/railties/guides/assets/stylesheets/main.css +++ b/railties/guides/assets/stylesheets/main.css @@ -341,6 +341,14 @@ h6 { margin-top: 0.25em; } +#mainCol dd.kindle, #subCol dd.kindle { + background: #d5e9f6 url(../images/tab_info.gif) no-repeat left top; + border: none; + padding: 1.25em 1em 1.25em 48px; + margin-left: 0; + margin-top: 0.25em; +} + #mainCol div.warning, #subCol dd.warning { background: #f9d9d8 url(../images/tab_red.gif) no-repeat left top; border: none; diff --git a/railties/guides/code/getting_started/config/environments/development.rb b/railties/guides/code/getting_started/config/environments/development.rb index d60a10568b..aefd25c6b6 100644 --- a/railties/guides/code/getting_started/config/environments/development.rb +++ b/railties/guides/code/getting_started/config/environments/development.rb @@ -6,9 +6,6 @@ Blog::Application.configure do # since you don't have to restart the web server when you make code changes. config.cache_classes = false - # Log error messages when you accidentally call methods on nil. - config.whiny_nils = true - # Show full error reports and disable caching config.consider_all_requests_local = true config.action_controller.perform_caching = false diff --git a/railties/guides/code/getting_started/config/environments/test.rb b/railties/guides/code/getting_started/config/environments/test.rb index 08697cbbc9..1d45541d5c 100644 --- a/railties/guides/code/getting_started/config/environments/test.rb +++ b/railties/guides/code/getting_started/config/environments/test.rb @@ -11,9 +11,6 @@ Blog::Application.configure do config.serve_static_assets = true config.static_cache_control = "public, max-age=3600" - # Log error messages when you accidentally call methods on nil - config.whiny_nils = true - # Show full error reports and disable caching config.consider_all_requests_local = true config.action_controller.perform_caching = false diff --git a/railties/guides/rails_guides/generator.rb b/railties/guides/rails_guides/generator.rb index 6f6d3bda80..49ad8f7769 100644 --- a/railties/guides/rails_guides/generator.rb +++ b/railties/guides/rails_guides/generator.rb @@ -47,6 +47,11 @@ # Set to "1" to indicate generated guides should be marked as edge. This # inserts a badge and changes the preamble of the home page. # +# KINDLE +# Set to "1" to generate the .mobi with all the guides. The kindlegen +# executable must be in your PATH. You can get it for free from +# http://www.amazon.com/kindlepublishing +# # --------------------------------------------------------------------------- require 'set' @@ -65,37 +70,81 @@ module RailsGuides class Generator attr_reader :guides_dir, :source_dir, :output_dir, :edge, :warnings, :all - GUIDES_RE = /\.(?:textile|html\.erb)$/ + GUIDES_RE = /\.(?:textile|erb)$/ def initialize(output=nil) - @lang = ENV['GUIDES_LANGUAGE'] + set_flags_from_environment + + if kindle? + check_for_kindlegen + register_kindle_mime_types + end + initialize_dirs(output) create_output_dir_if_needed - set_flags_from_environment + end + + def set_flags_from_environment + @edge = ENV['EDGE'] == '1' + @warnings = ENV['WARNINGS'] == '1' + @all = ENV['ALL'] == '1' + @kindle = ENV['KINDLE'] == '1' + @version = ENV['RAILS_VERSION'] || `git rev-parse --short HEAD`.chomp + @lang = ENV['GUIDES_LANGUAGE'] + end + + def register_kindle_mime_types + Mime::Type.register_alias("application/xml", :opf, %w(opf)) + Mime::Type.register_alias("application/xml", :ncx, %w(ncx)) end def generate generate_guides copy_assets + generate_mobi if kindle? end private + + def kindle? + @kindle + end + + def check_for_kindlegen + if `which kindlegen`.blank? + raise "Can't create a kindle version without `kindlegen`." + end + end + + def generate_mobi + opf = "#{output_dir}/rails_guides.opf" + out = "#{output_dir}/kindlegen.out" + + system "kindlegen #{opf} -o #{mobi} > #{out} 2>&1" + puts "Guides compiled as Kindle book to #{mobi}" + puts "(kindlegen log at #{out})." + end + + def mobi + "ruby_on_rails_guides_#@version%s.mobi" % (@lang.present? ? ".#@lang" : '') + end + def initialize_dirs(output) @guides_dir = File.join(File.dirname(__FILE__), '..') - @source_dir = File.join(@guides_dir, "source", @lang.to_s) - @output_dir = output || File.join(@guides_dir, "output", @lang.to_s) + @source_dir = "#@guides_dir/source/#@lang" + @output_dir = if output + output + elsif kindle? + "#@guides_dir/output/kindle/#@lang" + else + "#@guides_dir/output/#@lang" + end.sub(%r</$>, '') end def create_output_dir_if_needed FileUtils.mkdir_p(output_dir) end - def set_flags_from_environment - @edge = ENV['EDGE'] == '1' - @warnings = ENV['WARNINGS'] == '1' - @all = ENV['ALL'] == '1' - end - def generate_guides guides_to_generate.each do |guide| output_file = output_file_for(guide) @@ -105,6 +154,13 @@ module RailsGuides def guides_to_generate guides = Dir.entries(source_dir).grep(GUIDES_RE) + + if kindle? + Dir.entries("#{source_dir}/kindle").grep(GUIDES_RE).map do |entry| + guides << "kindle/#{entry}" + end + end + ENV.key?('ONLY') ? select_only(guides) : guides end @@ -120,36 +176,47 @@ module RailsGuides end def output_file_for(guide) - guide.sub(GUIDES_RE, '.html') + if guide =~/\.textile$/ + guide.sub(/\.textile$/, '.html') + else + guide.sub(/\.erb$/, '') + end + end + + def output_path_for(output_file) + File.join(output_dir, File.basename(output_file)) end def generate?(source_file, output_file) fin = File.join(source_dir, source_file) - fout = File.join(output_dir, output_file) + fout = output_path_for(output_file) all || !File.exists?(fout) || File.mtime(fout) < File.mtime(fin) end def generate_guide(guide, output_file) - puts "Generating #{output_file}" - File.open(File.join(output_dir, output_file), 'w') do |f| - view = ActionView::Base.new(source_dir, :edge => edge) + output_path = output_path_for(output_file) + puts "Generating #{guide} as #{output_file}" + layout = kindle? ? 'kindle/layout' : 'layout' + + File.open(output_path, 'w') do |f| + view = ActionView::Base.new(source_dir, :edge => @edge, :version => @version, :mobi => "kindle/#{mobi}") view.extend(Helpers) - if guide =~ /\.html\.erb$/ + if guide =~ /\.(\w+)\.erb$/ # Generate the special pages like the home. # Passing a template handler in the template name is deprecated. So pass the file name without the extension. - result = view.render(:layout => 'layout', :file => $`) + result = view.render(:layout => layout, :formats => [$1], :file => $`) else body = File.read(File.join(source_dir, guide)) body = set_header_section(body, view) body = set_index(body, view) - result = view.render(:layout => 'layout', :text => textile(body)) + result = view.render(:layout => layout, :text => textile(body)) warn_about_broken_links(result) if @warnings end - f.write result + f.write(result) end end @@ -216,7 +283,7 @@ module RailsGuides anchors = Set.new html.scan(/<h\d\s+id="([^"]+)/).flatten.each do |anchor| if anchors.member?(anchor) - puts "*** DUPLICATE ID: #{anchor}, please put and explicit ID, e.g. h4(#explicit-id), or consider rewording" + puts "*** DUPLICATE ID: #{anchor}, please use an explicit ID, e.g. h4(#explicit-id), or consider rewording" else anchors << anchor end diff --git a/railties/guides/rails_guides/helpers.rb b/railties/guides/rails_guides/helpers.rb index 463df8a7a8..45ad9b9588 100644 --- a/railties/guides/rails_guides/helpers.rb +++ b/railties/guides/rails_guides/helpers.rb @@ -11,6 +11,14 @@ module RailsGuides result << content_tag(:dd, capture(&block)) result end + + def documents_by_section + @documents_by_section ||= YAML.load_file(File.expand_path('../../source/documents.yaml', __FILE__)) + end + + def documents_flat + documents_by_section.map {|section| section['documents']}.flatten + end def author(name, nick, image = 'credits_pic_blank.gif', &block) image = "images/#{image}" diff --git a/railties/guides/source/3_2_release_notes.textile b/railties/guides/source/3_2_release_notes.textile new file mode 100644 index 0000000000..c1afee004e --- /dev/null +++ b/railties/guides/source/3_2_release_notes.textile @@ -0,0 +1,443 @@ +h2. Ruby on Rails 3.2 Release Notes + +Highlights in Rails 3.2: + +* Faster Development Mode +* New Routing Engine +* Automatic Query Explains +* Tagged Logging + +These release notes cover the major changes, but don't include every little bug fix and change. If you want to see everything, check out the "list of commits":https://github.com/rails/rails/commits/3-2-stable in the main Rails repository on GitHub. + +endprologue. + +h3. Upgrading to Rails 3.2 + +If you're upgrading an existing application, it's a great idea to have good test coverage before going in. You should also first upgrade to Rails 3.1 in case you haven't and make sure your application still runs as expected before attempting to update to Rails 3.2. Then take heed of the following changes: + +h4. Rails 3.2 requires at least Ruby 1.8.7 + +Rails 3.2 requires Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially and you should upgrade as early as possible. Rails 3.2 is also compatible with Ruby 1.9.2. + +TIP: Note that Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition have these fixed since release 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump on 1.9.2 or 1.9.3 for smooth sailing. + +h3. Creating a Rails 3.2 application + +<shell> +# You should have the 'rails' rubygem installed +$ rails new myapp +$ cd myapp +</shell> + +h4. Vendoring Gems + +Rails now uses a +Gemfile+ in the application root to determine the gems you require for your application to start. This +Gemfile+ is processed by the "Bundler":https://github.com/carlhuda/bundler gem, which then installs all your dependencies. It can even install all the dependencies locally to your application so that it doesn't depend on the system gems. + +More information: - "Bundler homepage":http://gembundler.com + +h4. Living on the Edge + ++Bundler+ and +Gemfile+ makes freezing your Rails application easy as pie with the new dedicated +bundle+ command. If you want to bundle straight from the Git repository, you can pass the +--edge+ flag: + +<shell> +$ rails new myapp --edge +</shell> + +If you have a local checkout of the Rails repository and want to generate an application using that, you can pass the +--dev+ flag: + +<shell> +$ ruby /path/to/rails/bin/rails new myapp --dev +</shell> + +h3. Major Features + +h4. Faster Development Mode & Routing + +Rails 3.2 comes with a development mode that's noticeably faster. Inspired by "Active Reload":https://github.com/paneq/active_reload, Rails reloads classes only when files actually change. The performance gains are dramatic on a larger application. Route recognition also got a bunch faster thanks to the new "Journey":https://github.com/rails/journey engine. + +h4. Automatic Query Explains + +Rails 3.2 comes with a nice feature that explains queries generated by ARel by defining an +explain+ method in <tt>ActiveRecord::Relation</tt>. For example, you can run something like <tt>puts Person.active.limit(5).explain</tt> and the query ARel produces is explained. This allows to check for the proper indexes and further optimizations. + +Queries that take more than half a second to run are *automatically* explained in the development mode. This threshold, of course, can be changed. + +h4. Tagged Logging + +When running a multi-user, multi-account application, it's a great help to be able to filter the log by who did what. TaggedLogging in Active Support helps in doing exactly that by stamping log lines with subdomains, request ids, and anything else to aid debugging of such applications. + +h3. Railties + +* Speed up development by only reloading classes if dependencies files changed. This can be turned off by setting <tt>config.reload_classes_only_on_change</tt> to false. + +* New applications get a flag <tt>config.active_record.auto_explain_threshold_in_seconds</tt> in the environments configuration files. With a value of <tt>0.5</tt> in <tt>development.rb</tt> and commented out in <tt>production.rb</tt>. No mention in <tt>test.rb</tt>. + +* Added <tt>config.exceptions_app</tt> to set the exceptions application invoked by the +ShowException+ middleware when an exception happens. Defaults to <tt>ActionDispatch::PublicExceptions.new(Rails.public_path)</tt>. + +* Added a <tt>DebugExceptions</tt> middleware which contains features extracted from <tt>ShowExceptions</tt> middleware. + +* Display mounted engines' routes in <tt>rake routes</tt>. + +* Allow to change the loading order of railties with <tt>config.railties_order</tt> like: + +<ruby> +config.railties_order = [Blog::Engine, :main_app, :all] +</ruby> + +* Scaffold returns 204 No Content for API requests without content. This makes scaffold work with jQuery out of the box. + +* Update Rails::Rack::Logger middleware to apply any tags set in config.log_tags to the newly ActiveSupport::TaggedLogging Rails.logger. This makes it easy to tag log lines with debug information like subdomain and request id -- both very helpful in debugging multi-user production applications + +* Default options to +rails new+ can be set in <tt>~/.railsrc</tt>. + +* Add an alias +d+ for +destroy+. This works for engines too. + +* Attributes on scaffold and model generators default to string. This allows the following: <tt>rails g scaffold Post title body:text author</tt> + +* Allow scaffold/model/migration generators to accept "index" and "uniq" modifiers. For example, + +<ruby> +rails g scaffold Post title:string:index author:uniq price:decimal{7,2} +</ruby> + +will create indexes for +title+ and +author+ with the latter being an unique index. Some types such as decimal accept custom options. In the example, +price+ will be a decimal column with precision and scale set to 7 and 2 respectively. + +* Remove old plugin generator +rails generate plugin+ in favor of +rails plugin new+ command. + +* Remove old <tt>config.paths.app.controller</tt> API in favor of <tt>config.paths["app/controller"]</tt>. + +h3. Action Pack + +h4. Action Controller + +* Make <tt>ActiveSupport::Benchmarkable</tt> a default module for <tt>ActionController::Base,</tt> so the <tt>#benchmark</tt> method is once again available in the controller context like it used to be. + +* Added +:gzip+ option to +caches_page+. The default option can be configured globally using <tt>page_cache_compression</tt>. + +* Rails will now use your default layout (such as "layouts/application") when you specify a layout with <tt>:only</tt> and <tt>:except</tt> condition, and those conditions fail. + +<ruby> +class CarsController + layout 'single_car', :only => :show +end +</ruby> + +Rails will use 'layouts/single_car' when a request comes in :show action, and use 'layouts/application' (or 'layouts/cars', if exists) when a request comes in for any other actions. + +* form_for is changed to use "#{action}_#{as}" as the css class and id if +:as+ option is provided. Earlier versions used "#{as}_#{action}". + +* <tt>ActionController::ParamsWrapper</tt> on ActiveRecord models now only wrap <tt>attr_accessible</tt> attributes if they were set. If not, only the attributes returned by the class method +attribute_names+ will be wrapped. This fixes the wrapping of nested attributes by adding them to +attr_accessible+. + +* Log "Filter chain halted as CALLBACKNAME rendered or redirected" every time a before callback halts. + +* <tt>ActionDispatch::ShowExceptions</tt> is refactored. The controller is responsible for choosing to show exceptions. It's possible to override +show_detailed_exceptions?+ in controllers to specify which requests should provide debugging information on errors. + +* Responders now return 204 No Content for API requests without a response body (as in the new scaffold). + +* <tt>ActionController::TestCase</tt> cookies is refactored. Assigning cookies for test cases should now use <tt>cookies[]</tt> + +<ruby> +cookies[:email] = 'user@example.com' +get :index +assert_equal 'user@example.com', cookies[:email] +</ruby> + +To clear the cookies, use +clear+. + +<ruby> +cookies.clear +get :index +assert_nil cookies[:email] +</ruby> + +We now no longer write out HTTP_COOKIE and the cookie jar is persistent between requests so if you need to manipulate the environment for your test you need to do it before the cookie jar is created. + +* <tt>send_file</tt> now guesses the MIME type from the file extension if +:type+ is not provided. + +* MIME type entries for PDF, ZIP and other formats were added. + +* Allow fresh_when/stale? to take a record instead of an options hash. + +* Changed log level of warning for missing CSRF token from <tt>:debug</tt> to <tt>:warn</tt>. + +* Assets should use the request protocol by default or default to relative if no request is available. + +h5. Deprecations + +* Deprecated implied layout lookup in controllers whose parent had a explicit layout set: + +<ruby> +class ApplicationController + layout "application" +end + +class PostsController < ApplicationController +end +</ruby> + +In the example above, Posts controller will no longer automatically look up for a posts layout. If you need this functionality you could either remove <tt>layout "application"</tt> from +ApplicationController+ or explicitly set it to +nil+ in +PostsController+. + +h4. Action Dispatch + +* Added <tt>ActionDispatch::RequestId</tt> middleware that'll make a unique X-Request-Id header available to the response and enables the <tt>ActionDispatch::Request#uuid</tt> method. This makes it easy to trace requests from end-to-end in the stack and to identify individual requests in mixed logs like Syslog. + +* The <tt>ShowExceptions</tt> middleware now accepts a exceptions application that is responsible to render an exception when the application fails. The application is invoked with a copy of the exception in +env["action_dispatch.exception"]+ and with the <tt>PATH_INFO</tt> rewritten to the status code. + +* Allow rescue responses to be configured through a railtie as in <tt>config.action_dispatch.rescue_responses</tt>. + +h4. Action View + +* Add +button_tag+ support to <tt>ActionView::Helpers::FormBuilder</tt>. This support mimics the default behavior of +submit_tag+. + +<ruby> +<%= form_for @post do |f| %> + <%= f.button %> +<% end %> +</ruby> + +* Date helpers accept a new option <tt>:use_two_digit_numbers => true</tt>, that renders select boxes for months and days with a leading zero without changing the respective values. For example, this is useful for displaying ISO8601-style dates such as '2011-08-01'. + +* You can provide a namespace for your form to ensure uniqueness of id attributes on form elements. The namespace attribute will be prefixed with underscore on the generated HTML id. + +<ruby> +<%= form_for(@offer, :namespace => 'namespace') do |f| %> + <%= f.label :version, 'Version' %>: + <%= f.text_field :version %> +<% end %> +</ruby> + +* Limit the number of options for +select_year+ to 1000. Pass +:max_years_allowed+ option to set your own limit. + +* +content_tag_for+ and +div_for+ can now take a collection of records. It will also yield the record as the first argument if you set a receiving argument in your block. So instead of having to do this: + +<ruby> +@items.each do |item| + content_tag_for(:li, item) do + Title: <%= item.title %> + end +end +</ruby> + +You can do this: + +<ruby> +content_tag_for(:li, @items) do |item| + Title: <%= item.title %> +end +</ruby> + +h5. Deprecations + +* Passing formats or handlers to render :template and friends like <tt>render :template => "foo.html.erb"</tt> is deprecated. Instead, you can provide :handlers and :formats directly as an options: <tt> render :template => "foo", :formats => [:html, :js], :handlers => :erb</tt>. + +h3. Active Record + +* Implemented <tt>ActiveRecord::Relation#explain</tt>. + +* Implements <tt>AR::Base.silence_auto_explain</tt> which allows the user to selectively disable automatic EXPLAINs within a block. + +* Implements automatic EXPLAIN logging for slow queries. A new configuration parameter +config.active_record.auto_explain_threshold_in_seconds+ determines what's to be considered a slow query. Setting that to nil disables this feature. Defaults are 0.5 in development mode, and nil in test and production modes. As of this writing there's support for SQLite, MySQL (mysql2 adapter), and PostgreSQL. + +* Added <tt>ActiveRecord::Base.store</tt> for declaring simple single-column key/value stores. + +<ruby> +class User < ActiveRecord::Base + store :settings, accessors: [ :color, :homepage ] +end + +u = User.new(color: 'black', homepage: '37signals.com') +u.color # Accessor stored attribute +u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor +</ruby> + +* Added ability to run migrations only for a given scope, which allows to run migrations only from one engine (for example to revert changes from an engine that need to be removed). + +<ruby> +rake db:migrate SCOPE=blog +</ruby> + +* Migrations copied from engines are now scoped with engine's name, for example <tt>01_create_posts.blog.rb</tt>. + +* Implemented <tt>ActiveRecord::Relation#pluck</tt> method that returns an array of column values directly from the underlying table. This also works with serialized attributes. + +<ruby> +Client.where(:active => true).pluck(:id) +# SELECT id from clients where active = 1 +</ruby> + +* Generated association methods are created within a separate module to allow overriding and composition. For a class named MyModel, the module is named <tt>MyModel::GeneratedFeatureMethods</tt>. It is included into the model class immediately after the +generated_attributes_methods+ module defined in Active Model, so association methods override attribute methods of the same name. + +* Add <tt>ActiveRecord::Relation#uniq</tt> for generating unique queries. + +<ruby> +Client.select('DISTINCT name') +</ruby> + +..can be written as: + +<ruby> +Client.select(:name).uniq +</ruby> + +This also allows you to revert the uniqueness in a relation: + +<ruby> +Client.select(:name).uniq.uniq(false) +</ruby> + +* Support index sort order in SQLite, MySQL and PostgreSQL adapters. + +* Allow the +:class_name+ option for associations to take a symbol in addition to a string. This is to avoid confusing newbies, and to be consistent with the fact that other options like :foreign_key already allow a symbol or a string. + +<ruby> +has_many :clients, :class_name => :Client # Note that the symbol need to be capitalized +</ruby> + +* In development mode, <tt>db:drop</tt> also drops the test database in order to be symmetric with <tt>db:create</tt>. + +* Case-insensitive uniqueness validation avoids calling LOWER in MySQL when the column already uses a case-insensitive collation. + +* Transactional fixtures enlist all active database connections. You can test models on different connections without disabling transactional fixtures. + +* Add +first_or_create+, +first_or_create!+, +first_or_initialize+ methods to Active Record. This is a better approach over the old +find_or_create_by+ dynamic methods because it's clearer which arguments are used to find the record and which are used to create it. + +<ruby> +User.where(:first_name => "Scarlett").first_or_create!(:last_name => "Johansson") +</ruby> + +h4. Deprecations + +* Automatic closure of connections in threads is deprecated. For example the following code is deprecated: + +<ruby> +Thread.new { Post.find(1) }.join +</ruby> + +It should be changed to close the database connection at the end of the thread: + +<ruby> +Thread.new { + Post.find(1) + Post.connection.close +}.join +</ruby> + +Only people who spawn threads in their application code need to worry about this change. + +* The +set_table_name+, +set_inheritance_column+, +set_sequence_name+, +set_primary_key+, +set_locking_column+ methods are deprecated. Use an assignment method instead. For example, instead of +set_table_name+, use <tt>self.table_name=</tt>. + +<ruby> +class Project < ActiveRecord::Base + self.table_name = "project" +end +</ruby> + +Or define your own <tt>self.table_name</tt> method: + +<ruby> +class Post < ActiveRecord::Base + def self.table_name + "special_" + super + end +end + +Post.table_name # => "special_posts" + +</ruby> + +h3. Active Model + +* Add <tt>ActiveModel::Errors#added?</tt> to check if a specific error has been added. + +* Add ability to define strict validations with <tt>strict => true</tt> that always raises exception when fails. + +* Provide mass_assignment_sanitizer as an easy API to replace the sanitizer behavior. Also support both :logger (default) and :strict sanitizer behavior. + +h4. Deprecations + +* Deprecated <tt>define_attr_method</tt> in <tt>ActiveModel::AttributeMethods</tt> because this only existed to support methods like +set_table_name+ in Active Record, which are themselves being deprecated. + +* Deprecated <tt>Model.model_name.partial_path</tt> in favor of <tt>model.to_partial_path</tt>. + +h3. Active Resource + +* Redirect responses: 303 See Other and 307 Temporary Redirect now behave like 301 Moved Permanently and 302 Found. + +h3. Active Support + +* Added <tt>ActiveSupport:TaggedLogging</tt> that can wrap any standard +Logger+ class to provide tagging capabilities. + +<ruby> +Logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT)) + +Logger.tagged("BCX") { Logger.info "Stuff" } +# Logs "[BCX] Stuff" + +Logger.tagged("BCX", "Jason") { Logger.info "Stuff" } +# Logs "[BCX] [Jason] Stuff" + +Logger.tagged("BCX") { Logger.tagged("Jason") { Logger.info "Stuff" } } +# Logs "[BCX] [Jason] Stuff" +</ruby> + +* The +beginning_of_week+ method in +Date+, +Time+ and +DateTime+ accepts an optional argument representing the day in which the week is assumed to start. + +* <tt>ActiveSupport::Notifications.subscribed</tt> provides subscriptions to events while a block runs. + +* Defined new methods <tt>Module#qualified_const_defined?</tt>, <tt>Module#qualified_const_get</tt> and <tt>Module#qualified_const_set</tt> that are analogous to the corresponding methods in the standard API, but accept qualified constant names. + +* Added +#deconstantize+ which complements +#demodulize+ in inflections. This removes the rightmost segment in a qualified constant name. + +* Added <tt>safe_constantize</tt> that constantizes a string but returns +nil+ instead of raising an exception if the constant (or part of it) does not exist. + +* <tt>ActiveSupport::OrderedHash</tt> is now marked as extractable when using <tt>Array#extract_options!</tt>. + +* Added <tt>Array#prepend</tt> as an alias for <tt>Array#unshift</tt> and <tt>Array#append</tt> as an alias for <tt>Array#<<</tt>. + +* The definition of a blank string for Ruby 1.9 has been extended to Unicode whitespace. Also, in Ruby 1.8 the ideographic space U+3000 is considered to be whitespace. + +* The inflector understands acronyms. + +* Added <tt>Time#all_day</tt>, <tt>Time#all_week</tt>, <tt>Time#all_quarter</tt> and <tt>Time#all_year</tt> as a way of generating ranges. + +<ruby> +Event.where(:created_at => Time.now.all_week) +Event.where(:created_at => Time.now.all_day) +</ruby> + +* Added <tt>instance_accessor: false</tt> as an option to <tt>Class#cattr_accessor</tt> and friends. + +* <tt>ActiveSupport::OrderedHash</tt> now has different behavior for <tt>#each</tt> and <tt>#each_pair</tt> when given a block accepting its parameters with a splat. + +* Added <tt>ActiveSupport::Cache::NullStore</tt> for use in development and testing. + +* Removed <tt>ActiveSupport::SecureRandom</tt> in favor of <tt>SecureRandom</tt> from the standard library. + +h4. Deprecations + +* Deprecated <tt>ActiveSupport::Memoizable</tt> in favor of Ruby memoization pattern. + +* <tt>Module#synchronize</tt> is deprecated with no replacement. Please use monitor from ruby's standard library. + +* Deprecated <tt>ActiveSupport::MessageEncryptor#encrypt</tt> and <tt>ActiveSupport::MessageEncryptor#decrypt</tt>. + +* <tt>ActiveSupport::BufferedLogger#silence</tt> is deprecated. If you want to squelch logs for a certain block, change the log level for that block. + +* <tt>ActiveSupport::BufferedLogger#open_log</tt> is deprecated. This method should not have been public in the first place. + +* <tt>ActiveSupport::BufferedLogger's</tt> behavior of automatically creating the directory for your log file is deprecated. Please make sure to create the directory for your log file before instantiating. + +* <tt>ActiveSupport::BufferedLogger#auto_flushing</tt> is deprecated. Either set the sync level on the underlying file handle like this. Or tune your filesystem. The FS cache is now what controls flushing. + +<ruby> +f = File.open('foo.log', 'w') +f.sync = true +ActiveSupport::BufferedLogger.new f +</ruby> + +* <tt>ActiveSupport::BufferedLogger#flush</tt> is deprecated. Set sync on your filehandle, or tune your filesystem. + +h3. Credits + +See the "full list of contributors to Rails":http://contributors.rubyonrails.org/ for the many people who spent many hours making Rails, the stable and robust framework it is. Kudos to all of them. + +Rails 3.2 Release Notes were compiled by "Vijay Dev":https://github.com/vijaydev. diff --git a/railties/guides/source/_license.html.erb b/railties/guides/source/_license.html.erb new file mode 100644 index 0000000000..00b4466f50 --- /dev/null +++ b/railties/guides/source/_license.html.erb @@ -0,0 +1,2 @@ +<p>This work is licensed under a <a href="http://creativecommons.org/licenses/by-sa/3.0/">Creative Commons Attribution-Share Alike 3.0</a> License</p> +<p>"Rails", "Ruby on Rails", and the Rails logo are trademarks of David Heinemeier Hansson. All rights reserved.</p> diff --git a/railties/guides/source/_welcome.html.erb b/railties/guides/source/_welcome.html.erb new file mode 100644 index 0000000000..e5373ebca0 --- /dev/null +++ b/railties/guides/source/_welcome.html.erb @@ -0,0 +1,19 @@ +<h2>Ruby on Rails Guides</h2> + +<% if @edge %> +<p> + These are <b>Edge Guides</b>, based on the current <a href="https://github.com/rails/rails/tree/<%= @version %>">master</a> branch. +</p> +<p> + If you are looking for the ones for the stable version, please check + <a href="http://guides.rubyonrails.org">http://guides.rubyonrails.org</a> instead. +</p> +<% else %> +<p> + These are the new guides for Rails 3.1 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>. + These guides are designed to make you immediately productive with Rails, and to help you understand how all of the pieces fit together. +</p> +<% end %> +<p> + The guides for Rails 2.3.x are available at <a href="http://guides.rubyonrails.org/v2.3.11/">http://guides.rubyonrails.org/v2.3.11/</a>. +</p> diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index 0cbabd71a1..beada85ce3 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -966,7 +966,7 @@ When a +lambda+ is used for a +scope+, it can take arguments: <ruby> class Post < ActiveRecord::Base - scope :1_week_before, lambda { |time| where("created_at < ?", time) + scope :1_week_before, lambda { |time| where("created_at < ?", time) } end </ruby> diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 0296e27725..e912de974a 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -177,19 +177,6 @@ end NOTE: Defined in +active_support/core_ext/object/try.rb+. -h4. +singleton_class+ - -The method +singleton_class+ returns the singleton class of the receiver: - -<ruby> -String.singleton_class # => #<Class:String> -String.new.singleton_class # => #<Class:#<String:0x17a1d1c>> -</ruby> - -WARNING: Fixnums and symbols have no singleton classes, +singleton_class+ raises +TypeError+ on them. Moreover, the singleton classes of +nil+, +true+, and +false+, are +NilClass+, +TrueClass+, and +FalseClass+, respectively. - -NOTE: Defined in +active_support/core_ext/kernel/singleton_class.rb+. - h4. +class_eval(*args, &block)+ You can evaluate code in the context of any object's singleton class using +class_eval+: @@ -785,30 +772,6 @@ Absolute qualified constant names like +::Math::PI+ raise +NameError+. NOTE: Defined in +active_support/core_ext/module/qualified_const.rb+. -h4. Synchronization - -The +synchronize+ macro declares a method to be synchronized: - -<ruby> -class Counter - @@mutex = Mutex.new - attr_reader :value - - def initialize - @value = 0 - end - - def incr - @value += 1 # non-atomic - end - synchronize :incr, :with => '@@mutex' -end -</ruby> - -The method receives the name of an action, and a +:with+ option with code. The code is evaluated in the context of the receiver each time the method is invoked, and it should evaluate to a +Mutex+ instance or any other object that responds to +synchronize+ and accepts a block. - -NOTE: Defined in +active_support/core_ext/module/synchronization.rb+. - h4. Reachable A named module is reachable if it is stored in its corresponding constant. It means you can reach the module object via the constant. @@ -1851,43 +1814,6 @@ NOTE: Defined in +active_support/core_ext/string/inflections.rb+. h4(#string-conversions). Conversions -h5. +ord+ - -Ruby 1.9 defines +ord+ to be the codepoint of the first character of the receiver. Active Support backports +ord+ for single-byte encodings like ASCII or ISO-8859-1 in Ruby 1.8: - -<ruby> -"a".ord # => 97 -"à".ord # => 224, in ISO-8859-1 -</ruby> - -In Ruby 1.8 +ord+ doesn't work in general in UTF8 strings, use the multibyte support in Active Support for that: - -<ruby> -"a".mb_chars.ord # => 97 -"à".mb_chars.ord # => 224, in UTF8 -</ruby> - -Note that the 224 is different in both examples. In ISO-8859-1 "à" is represented as a single byte, 224. Its single-character representation in UTF8 has two bytes, namely 195 and 160, but its Unicode codepoint is 224. If we call +ord+ on the UTF8 string "à" the return value will be 195 in Ruby 1.8. That is not an error, because UTF8 is unsupported, the call itself would be bogus. - -INFO: +ord+ is equivalent to +getbyte(0)+. - -NOTE: Defined in +active_support/core_ext/string/conversions.rb+. - -h5. +getbyte+ - -Active Support backports +getbyte+ from Ruby 1.9: - -<ruby> -"foo".getbyte(0) # => 102, same as "foo".ord -"foo".getbyte(1) # => 111 -"foo".getbyte(9) # => nil -"foo".getbyte(-1) # => 111 -</ruby> - -INFO: +getbyte+ is equivalent to +[]+. - -NOTE: Defined in +active_support/core_ext/string/conversions.rb+. - h5. +to_date+, +to_time+, +to_datetime+ The methods +to_date+, +to_time+, and +to_datetime+ are basically convenience wrappers around +Date._parse+: @@ -1980,20 +1906,6 @@ h3. Extensions to +BigDecimal+ h3. Extensions to +Enumerable+ -h4. +group_by+ - -Active Support redefines +group_by+ in Ruby 1.8.7 so that it returns an ordered hash as in 1.9: - -<ruby> -entries_by_surname_initial = address_book.group_by do |entry| - entry.surname.at(0).upcase -end -</ruby> - -Distinct block return values are added to the hash as they come, so that's the resulting order. - -NOTE: Defined in +active_support/core_ext/enumerable.rb+. - h4. +sum+ The method +sum+ adds the elements of an enumerable: @@ -2041,42 +1953,6 @@ end NOTE: Defined in +active_support/core_ext/enumerable.rb+. -h4. +pluck+ - -The +pluck+ method collects the value of the passed method for each element and returns the result as an array. - -<ruby> -people.pluck(:name) # => [ "David Heinemeier Hansson", "Jamie Heinemeier Hansson" ] -</ruby> - -NOTE: Defined in +active_support/core_ext/enumerable.rb+. - -h4. +each_with_object+ - -The +inject+ method offers iteration with an accumulator: - -<ruby> -[2, 3, 4].inject(1) {|product, i| product*i } # => 24 -</ruby> - -The block is expected to return the value for the accumulator in the next iteration, and this makes building mutable objects a bit cumbersome: - -<ruby> -[1, 2].inject({}) {|h, i| h[i] = i**2; h} # => {1 => 1, 2 => 4} -</ruby> - -See that spurious "+; h+"? - -Active Support backports +each_with_object+ from Ruby 1.9, which addresses that use case. It iterates over the collection, passes the accumulator, and returns the accumulator when done. You normally modify the accumulator in place. The example above would be written this way: - -<ruby> -[1, 2].each_with_object({}) {|i, h| h[i] = i**2} # => {1 => 1, 2 => 4} -</ruby> - -WARNING. Note that the item of the collection and the accumulator come in different order in +inject+ and +each_with_object+. - -NOTE: Defined in +active_support/core_ext/enumerable.rb+. - h4. +index_by+ The method +index_by+ generates a hash with the elements of an enumerable indexed by some key. @@ -2148,20 +2024,6 @@ The methods +second+, +third+, +fourth+, and +fifth+ return the corresponding el NOTE: Defined in +active_support/core_ext/array/access.rb+. -h4. Random Access - -Active Support backports +sample+ from Ruby 1.9: - -<ruby> -shape_type = [Circle, Square, Triangle].sample -# => Square, for example - -shape_types = [Circle, Square, Triangle].sample(2) -# => [Triangle, Circle], for example -</ruby> - -NOTE: Defined in +active_support/core_ext/array/random_access.rb+. - h4. Adding Elements h5. +prepend+ @@ -2914,14 +2776,6 @@ WARNING: The original +Range#include?+ is still the one aliased to +Range#===+. NOTE: Defined in +active_support/core_ext/range/include_range.rb+. -h4. +cover?+ - -Ruby 1.9 provides +cover?+, and Active Support defines it for previous versions as an alias for +include?+. - -The method +include?+ in Ruby 1.9 is different from the one in 1.8 for non-numeric ranges: instead of being based on comparisons between the value and the range's endpoints, it walks the range with +succ+ looking for value. This works better for ranges with holes, but it has different complexity and may not finish in some other cases. - -In Ruby 1.9 the old behavior is still available in the new +cover?+, which Active Support backports for forward compatibility. For example, Rails uses +cover?+ for ranges in +validates_inclusion_of+. - h4. +overlaps?+ The method +Range#overlaps?+ says whether any two given ranges have non-void intersection: @@ -3591,12 +3445,6 @@ Time.utc_time(1582, 10, 3) + 5.days # => Mon Oct 18 00:00:00 UTC 1582 </ruby> -h3. Extensions to +Process+ - -h4. +daemon+ - -Ruby 1.9 provides +Process.daemon+, and Active Support defines it for previous versions. It accepts the same two arguments, whether it should chdir to the root directory (default, true), and whether it should inherit the standard file descriptors from the parent (default, false). - h3. Extensions to +File+ h4. +atomic_write+ diff --git a/railties/guides/source/caching_with_rails.textile b/railties/guides/source/caching_with_rails.textile index ec9bfd4d40..6419d32c13 100644 --- a/railties/guides/source/caching_with_rails.textile +++ b/railties/guides/source/caching_with_rails.textile @@ -64,6 +64,28 @@ end If you want a more complicated expiration scheme, you can use cache sweepers to expire cached objects when things change. This is covered in the section on Sweepers. +By default, page caching automatically gzips files (for example, to +products.html.gz+ if user requests +/products+) to reduce the size of data transmitted (web servers are typically configured to use a moderate compression ratio as a compromise, but since precompilation happens once, compression ratio is maximum). + +Nginx is able to serve compressed content directly from disk by enabling +gzip_static+: + +<plain> +location / { + gzip_static on; # to serve pre-gzipped version +} +</plain> + +You can disable gzipping by setting +:gzip+ option to false (for example, if action returns image): + +<ruby> +caches_page :image, :gzip => false +</ruby> + +Or, you can set custom gzip compression level (level names are taken from +Zlib+ constants): + +<ruby> +caches_page :image, :gzip => :best_speed +</ruby> + NOTE: Page caching ignores all parameters. For example +/products?page=1+ will be written out to the filesystem as +products.html+ with no reference to the +page+ parameter. Thus, if someone requests +/products?page=2+ later, they will get the cached first page. A workaround for this limitation is to include the parameters in the page's path, e.g. +/productions/page/1+. INFO: Page caching runs in an after filter. Thus, invalid requests won't generate spurious cache entries as long as you halt them. Typically, a redirection in some before filter that checks request preconditions does the job. diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile index 58855bc80b..fa783edc58 100644 --- a/railties/guides/source/command_line.textile +++ b/railties/guides/source/command_line.textile @@ -381,16 +381,16 @@ h4. +about+ <shell> $ rake about About your application's environment -Ruby version 1.8.7 (x86_64-linux) +Ruby version 1.9.3 (x86_64-linux) RubyGems version 1.3.6 Rack version 1.3 -Rails version 3.2.0.beta +Rails version 4.0.0.beta JavaScript Runtime Node.js (V8) -Active Record version 3.2.0.beta -Action Pack version 3.2.0.beta -Active Resource version 3.2.0.beta -Action Mailer version 3.2.0.beta -Active Support version 3.2.0.beta +Active Record version 4.0.0.beta +Action Pack version 4.0.0.beta +Active Resource version 4.0.0.beta +Action Mailer version 4.0.0.beta +Active Support version 4.0.0.beta Middleware ActionDispatch::Static, Rack::Lock, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, ActionDispatch::Head, Rack::ConditionalGet, Rack::ETag, ActionDispatch::BestStandardsSupport Application root /home/foobar/commandsapp Environment development diff --git a/railties/guides/source/documents.yaml b/railties/guides/source/documents.yaml new file mode 100644 index 0000000000..dccfefb4fb --- /dev/null +++ b/railties/guides/source/documents.yaml @@ -0,0 +1,153 @@ +- + name: Start Here + documents: + - + name: Getting Started with Rails + url: getting_started.html + description: Everything you need to know to install Rails and create your first application. +- + name: Models + documents: + - + name: Rails Database Migrations + url: migrations.html + description: This guide covers how you can use Active Record migrations to alter your database in a structured and organized manner. + - + name: Active Record Validations and Callbacks + url: active_record_validations_callbacks.html + description: This guide covers how you can use Active Record validations and callbacks. + - + name: Active Record Associations + url: association_basics.html + description: This guide covers all the associations provided by Active Record. + - + name: Active Record Query Interface + url: active_record_querying.html + description: This guide covers the database query interface provided by Active Record. +- + name: Views + documents: + - + name: Layouts and Rendering in Rails + url: layouts_and_rendering.html + description: This guide covers the basic layout features of Action Controller and Action View, including rendering and redirecting, using content_for blocks, and working with partials. + - + name: Action View Form Helpers + url: form_helpers.html + description: Guide to using built-in Form helpers. +- + name: Controllers + documents: + - + name: Action Controller Overview + url: action_controller_overview.html + description: This guide covers how controllers work and how they fit into the request cycle in your application. It includes sessions, filters, and cookies, data streaming, and dealing with exceptions raised by a request, among other topics. + - + name: Rails Routing from the Outside In + url: routing.html + description: This guide covers the user-facing features of Rails routing. If you want to understand how to use routing in your own Rails applications, start here. +- + name: Digging Deeper + documents: + - + name: Active Support Core Extensions + url: active_support_core_extensions.html + description: This guide documents the Ruby core extensions defined in Active Support. + - + name: Rails Internationalization API + url: i18n.html + description: This guide covers how to add internationalization to your applications. Your application will be able to translate content to different languages, change pluralization rules, use correct date formats for each country and so on. + - + name: Action Mailer Basics + url: action_mailer_basics.html + work_in_progress: true + description: This guide describes how to use Action Mailer to send and receive emails. + - + name: Testing Rails Applications + url: testing.html + work_in_progress: true + description: This is a rather comprehensive guide to doing both unit and functional tests in Rails. It covers everything from 'What is a test?' to the testing APIs. Enjoy. + - + name: Securing Rails Applications + url: security.html + description: This guide describes common security problems in web applications and how to avoid them with Rails. + - + name: Debugging Rails Applications + url: debugging_rails_applications.html + description: This guide describes how to debug Rails applications. It covers the different ways of achieving this and how to understand what is happening "behind the scenes" of your code. + - + name: Performance Testing Rails Applications + url: performance_testing.html + description: This guide covers the various ways of performance testing a Ruby on Rails application. + - + name: Configuring Rails Applications + url: configuring.html + description: This guide covers the basic configuration settings for a Rails application. + - + name: Rails Command Line Tools and Rake tasks + url: command_line.html + description: This guide covers the command line tools and rake tasks provided by Rails. + - + name: Caching with Rails + work_in_progress: true + url: caching_with_rails.html + description: Various caching techniques provided by Rails. + - + name: Asset Pipeline + url: asset_pipeline.html + description: This guide documents the asset pipeline. + - + name: The Rails Initialization Process + work_in_progress: true + url: initialization.html + description: This guide explains the internals of the Rails initialization process as of Rails 3.1 +- + name: Extending Rails + documents: + - + name: The Basics of Creating Rails Plugins + work_in_progress: true + url: plugins.html + description: This guide covers how to build a plugin to extend the functionality of Rails. + - + name: Rails on Rack + url: rails_on_rack.html + description: This guide covers Rails integration with Rack and interfacing with other Rack components. + - + name: Creating and Customizing Rails Generators + url: generators.html + description: This guide covers the process of adding a brand new generator to your extension or providing an alternative to an element of a built-in Rails generator (such as providing alternative test stubs for the scaffold generator). +- + name: Contributing to Ruby on Rails + documents: + - + name: Contributing to Ruby on Rails + url: contributing_to_ruby_on_rails.html + description: Rails is not 'somebody else's framework.' This guide covers a variety of ways that you can get involved in the ongoing development of Rails. + - + name: API Documentation Guidelines + url: api_documentation_guidelines.html + description: This guide documents the Ruby on Rails API documentation guidelines. + - + name: Ruby on Rails Guides Guidelines + url: ruby_on_rails_guides_guidelines.html + description: This guide documents the Ruby on Rails guides guidelines. +- + name: Release Notes + documents: + - + name: Ruby on Rails 3.1 Release Notes + url: 3_1_release_notes.html + description: Release notes for Rails 3.1. + - + name: Ruby on Rails 3.0 Release Notes + url: 3_0_release_notes.html + description: Release notes for Rails 3.0. + - + name: Ruby on Rails 2.3 Release Notes + url: 2_3_release_notes.html + description: Release notes for Rails 2.3. + - + name: Ruby on Rails 2.2 Release Notes + url: 2_2_release_notes.html + description: Release notes for Rails 2.2. diff --git a/railties/guides/source/generators.textile b/railties/guides/source/generators.textile index 7a863ccbc7..d93dcf40bf 100644 --- a/railties/guides/source/generators.textile +++ b/railties/guides/source/generators.textile @@ -48,7 +48,7 @@ end NOTE: +create_file+ is a method provided by +Thor::Actions+. Documentation for +create_file+ and other Thor methods can be found in "Thor's documentation":http://rdoc.info/github/wycats/thor/master/Thor/Actions.html -Our new generator is quite simple: it inherits from +Rails::Generators::Base+ and has one method definition. Each public method in the generator is executed when a generator is invoked. Finally, we invoke the +create_file+ method that will create a file at the given destination with the given content. If you are familiar with the Rails Application Templates API, you'll feel right at home with the new generators API. +Our new generator is quite simple: it inherits from +Rails::Generators::Base+ and has one method definition. When a generator is invoked, each public method in the generator is executed sequentially in the order that it is defined. Finally, we invoke the +create_file+ method that will create a file at the given destination with the given content. If you are familiar with the Rails Application Templates API, you'll feel right at home with the new generators API. To invoke our new generator, we just need to do: diff --git a/railties/guides/source/index.html.erb b/railties/guides/source/index.html.erb index c9a8c4fa5c..5439459b42 100644 --- a/railties/guides/source/index.html.erb +++ b/railties/guides/source/index.html.erb @@ -3,189 +3,28 @@ Ruby on Rails Guides <% end %> <% content_for :header_section do %> -<h2>Ruby on Rails Guides (<%= ENV['RAILS_VERSION'] || 'edge' %>)</h2> - -<% if @edge %> -<p> - These are <b>Edge Guides</b>, based on the current - <a href="https://github.com/rails/rails/tree/master">master branch</a>. -</p> -<p> - If you are looking for the ones for the stable version please check - <a href="http://guides.rubyonrails.org">http://guides.rubyonrails.org</a> instead. -</p> -<% else %> -<p> - These are the new guides for Rails 3. The guides for Rails 2.3 are still available - at <a href="http://guides.rubyonrails.org/v2.3.11/">http://guides.rubyonrails.org/v2.3.11/</a>. -</p> -<% end %> -<p> - These guides are designed to make you immediately productive with Rails, - and to help you understand how all of the pieces fit together. -</p> - +<%= render 'welcome' %> <% end %> <% content_for :index_section do %> <div id="subCol"> <dl> + <dd class="kindle">Rails Guides are also available for the <%= link_to 'Kindle', 'https://kindle.amazon.com' %> +and <%= link_to 'Free Kindle Reading Apps', 'http://www.amazon.com/gp/kindle/kcp' %> for the iPad, +iPhone, Mac, Android, etc. Download them from <%= link_to 'here', @mobi %>. + </dd> <dd class="work-in-progress">Guides marked with this icon are currently being worked on. While they might still be useful to you, they may contain incomplete information and even errors. You can help by reviewing them and posting your comments and corrections to the author.</dd> </dl> </div> <% end %> -<h3>Start Here</h3> - -<dl> -<%= guide('Getting Started with Rails', 'getting_started.html') do %> - <p>Everything you need to know to install Rails and create your first application.</p> -<% end %> -</dl> - -<h3>Models</h3> - -<dl> -<%= guide("Rails Database Migrations", 'migrations.html') do %> - <p>This guide covers how you can use Active Record migrations to alter your database in a structured and organized manner.</p> -<% end %> - -<%= guide("Active Record Validations and Callbacks", 'active_record_validations_callbacks.html') do %> - <p>This guide covers how you can use Active Record validations and callbacks.</p> -<% end %> - -<%= guide("Active Record Associations", 'association_basics.html') do %> - <p>This guide covers all the associations provided by Active Record.</p> -<% end %> - -<%= guide("Active Record Query Interface", 'active_record_querying.html') do %> - <p>This guide covers the database query interface provided by Active Record.</p> -<% end %> -</dl> - -<h3>Views</h3> - -<dl> -<%= guide("Layouts and Rendering in Rails", 'layouts_and_rendering.html') do %> - <p>This guide covers the basic layout features of Action Controller and Action View, including rendering and redirecting, using content_for blocks, and working with partials.</p> -<% end %> - -<%= guide("Action View Form Helpers", 'form_helpers.html', :work_in_progress => true) do %> - <p>Guide to using built-in Form helpers.</p> -<% end %> -</dl> - -<h3>Controllers</h3> - -<dl> -<%= guide("Action Controller Overview", 'action_controller_overview.html') do %> - <p>This guide covers how controllers work and how they fit into the request cycle in your application. It includes sessions, filters, and cookies, data streaming, and dealing with exceptions raised by a request, among other topics.</p> -<% end %> - -<%= guide("Rails Routing from the Outside In", 'routing.html') do %> - <p>This guide covers the user-facing features of Rails routing. If you want to understand how to use routing in your own Rails applications, start here.</p> -<% end %> -</dl> - -<h3>Digging Deeper</h3> - -<dl> - -<%= guide("Active Support Core Extensions", 'active_support_core_extensions.html') do %> - <p>This guide documents the Ruby core extensions defined in Active Support.</p> -<% end %> - -<%= guide("Rails Internationalization API", 'i18n.html') do %> - <p>This guide covers how to add internationalization to your applications. Your application will be able to translate content to different languages, change pluralization rules, use correct date formats for each country and so on.</p> -<% end %> - -<%= guide("Action Mailer Basics", 'action_mailer_basics.html', :work_in_progress => true) do %> - <p>This guide describes how to use Action Mailer to send and receive emails.</p> -<% end %> - -<%= guide("Testing Rails Applications", 'testing.html', :work_in_progress => true) do %> - <p>This is a rather comprehensive guide to doing both unit and functional tests in Rails. It covers everything from "What is a test?" to the testing APIs. Enjoy.</p> -<% end %> - -<%= guide("Securing Rails Applications", 'security.html') do %> - <p>This guide describes common security problems in web applications and how to avoid them with Rails.</p> -<% end %> - -<%= guide("Debugging Rails Applications", 'debugging_rails_applications.html') do %> - <p>This guide describes how to debug Rails applications. It covers the different ways of achieving this and how to understand what is happening "behind the scenes" of your code.</p> -<% end %> - -<%= guide("Performance Testing Rails Applications", 'performance_testing.html') do %> - <p>This guide covers the various ways of performance testing a Ruby on Rails application.</p> -<% end %> - -<%= guide("Configuring Rails Applications", 'configuring.html') do %> - <p>This guide covers the basic configuration settings for a Rails application.</p> -<% end %> - -<%= guide("Rails Command Line Tools and Rake tasks", 'command_line.html') do %> - <p>This guide covers the command line tools and rake tasks provided by Rails.</p> -<% end %> - -<%= guide("Caching with Rails", 'caching_with_rails.html', :work_in_progress => true) do %> - <p>Various caching techniques provided by Rails.</p> -<% end %> - -<%= guide('Asset Pipeline', 'asset_pipeline.html') do %> - <p>This guide documents the asset pipeline.</p> -<% end %> -</dl> - -<h3>Extending Rails</h3> - -<dl> - <%= guide("The Basics of Creating Rails Plugins", 'plugins.html', :work_in_progress => true) do %> - <p>This guide covers how to build a plugin to extend the functionality of Rails.</p> - <% end %> - - <%= guide("Rails on Rack", 'rails_on_rack.html') do %> - <p>This guide covers Rails integration with Rack and interfacing with other Rack components.</p> - <% end %> - - <%= guide("Creating and Customizing Rails Generators", 'generators.html') do %> - <p>This guide covers the process of adding a brand new generator to your extension - or providing an alternative to an element of a built-in Rails generator (such as - providing alternative test stubs for the scaffold generator).</p> - <% end %> -</dl> - -<h3>Contributing to Ruby on Rails</h3> - -<dl> - <%= guide("Contributing to Ruby on Rails", 'contributing_to_ruby_on_rails.html') do %> - <p>Rails is not "somebody else's framework." This guide covers a variety of ways that you can get involved in the ongoing development of Rails.</p> - <% end %> - - <%= guide('API Documentation Guidelines', 'api_documentation_guidelines.html') do %> - <p>This guide documents the Ruby on Rails API documentation guidelines.</p> - <% end %> - - <%= guide('Ruby on Rails Guides Guidelines', 'ruby_on_rails_guides_guidelines.html') do %> - <p>This guide documents the Ruby on Rails guides guidelines.</p> - <% end %> -</dl> - -<h3>Release Notes</h3> - -<dl> -<%= guide("Ruby on Rails 3.1 Release Notes", '3_1_release_notes.html') do %> - <p>Release notes for Rails 3.1.</p> -<% end %> - -<%= guide("Ruby on Rails 3.0 Release Notes", '3_0_release_notes.html') do %> - <p>Release notes for Rails 3.0.</p> -<% end %> - -<%= guide("Ruby on Rails 2.3 Release Notes", '2_3_release_notes.html') do %> - <p>Release notes for Rails 2.3.</p> -<% end %> - -<%= guide("Ruby on Rails 2.2 Release Notes", '2_2_release_notes.html') do %> - <p>Release notes for Rails 2.2.</p> +<% documents_by_section.each do |section| %> + <h3><%= section['name'] %></h3> + <dl> + <% section['documents'].each do |document| %> + <%= guide(document['name'], document['url'], :work_in_progress => document['work_in_progress']) do %> + <p><%= document['description'] %></p> + <% end %> + <% end %> + </dl> <% end %> -</dl> diff --git a/railties/guides/source/kindle/KINDLE.md b/railties/guides/source/kindle/KINDLE.md new file mode 100644 index 0000000000..a7d9a4e4cf --- /dev/null +++ b/railties/guides/source/kindle/KINDLE.md @@ -0,0 +1,26 @@ +# Rails Guides on the Kindle + + +## Synopsis + + 1. Obtain `kindlegen` from the link below and put the binary in your path + 2. Run `KINDLE=1 rake generate_guides` to generate the guides and compile the `.mobi` file + 3. Copy `output/kindle/rails_guides.mobi` to your Kindle + +## Resources + + * [StackOverflow: Kindle Periodical Format](http://stackoverflow.com/questions/5379565/kindle-periodical-format) + * Example Periodical [.ncx](https://gist.github.com/808c971ed087b839d462) and [.opf](https://gist.github.com/d6349aa8488eca2ee6d0) + * [Kindle Publishing guidelines](http://kindlegen.s3.amazonaws.com/AmazonKindlePublishingGuidelines.pdf) + * [KindleGen & Kindle Previewer](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000234621) + +## TODO + +### Post release + + * Integrate generated Kindle document in to published HTML guides + * Tweak heading styles (most docs use h3/h4/h5, which end up being smaller than the text under it) + * Tweak table styles (smaller text? Many of the tables are unusable on a Kindle in portrait mode) + * Have the HTML/XML TOC 'drill down' into the TOCs of the individual guides + * `.epub` generation. + diff --git a/railties/guides/source/kindle/copyright.html.erb b/railties/guides/source/kindle/copyright.html.erb new file mode 100644 index 0000000000..bd51d87383 --- /dev/null +++ b/railties/guides/source/kindle/copyright.html.erb @@ -0,0 +1 @@ +<%= render 'license' %>
\ No newline at end of file diff --git a/railties/guides/source/kindle/layout.html.erb b/railties/guides/source/kindle/layout.html.erb new file mode 100644 index 0000000000..f0a286210b --- /dev/null +++ b/railties/guides/source/kindle/layout.html.erb @@ -0,0 +1,27 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> + +<title><%= yield(:page_title) || 'Ruby on Rails Guides' %></title> + +<link rel="stylesheet" type="text/css" href="stylesheets/kindle.css" /> + +</head> +<body class="guide"> + + <% if content_for? :header_section %> + <%= yield :header_section %> + <div class="pagebreak"> + <% end %> + + <% if content_for? :index_section %> + <%= yield :index_section %> + <div class="pagebreak"> + <% end %> + + <%= yield.html_safe %> +</body> +</html> diff --git a/railties/guides/source/kindle/rails_guides.opf.erb b/railties/guides/source/kindle/rails_guides.opf.erb new file mode 100644 index 0000000000..4e07664fd0 --- /dev/null +++ b/railties/guides/source/kindle/rails_guides.opf.erb @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> + +<package xmlns="http://www.idpf.org/2007/opf" version="2.0" unique-identifier="RailsGuides"> +<metadata> + <meta name="cover" content="cover" /> + <dc-metadata xmlns:dc="http://purl.org/dc/elements/1.1/"> + + <dc:title>Ruby on Rails Guides (<%= @version %>)</dc:title> + + <dc:language>en-us</dc:language> + <dc:creator>Ruby on Rails</dc:creator> + <dc:publisher>Ruby on Rails</dc:publisher> + <dc:subject>Reference</dc:subject> + <dc:date><%= Time.now.strftime('%Y-%m-%d') %></dc:date> + + <dc:description>These guides are designed to make you immediately productive with Rails, and to help you understand how all of the pieces fit together.</dc:description> + </dc-metadata> + <x-metadata> + <output content-type="application/x-mobipocket-subscription-magazine" encoding="utf-8"/> + </x-metadata> +</metadata> + +<manifest> + <!-- HTML content files [mandatory] --> + <% documents_flat.each do |document| %> + <item id="<%= document['url'] %>" media-type="text/html" href="<%= document['url'] %>" /> + <% end %> + + <% %w{toc.html credits.html welcome.html copyright.html}.each do |url| %> + <item id="<%= url %>" media-type="text/html" href="<%= url %>" /> + <% end %> + + <item id="toc" media-type="application/x-dtbncx+xml" href="toc.ncx" /> + + <item id="cover" media-type="image/jpeg" href="images/rails_guides_kindle_cover.jpg"/> +</manifest> + +<spine toc="toc"> + <itemref idref="toc.html" /> + <itemref idref="welcome.html" /> + <itemref idref="credits.html" /> + <itemref idref="copyright.html" /> + <% documents_flat.each do |document| %> + <itemref idref="<%= document['url'] %>" /> + <% end %> +</spine> + +<guide> + <reference type="toc" title="Table of Contents" href="toc.html"></reference> +</guide> + +</package> diff --git a/railties/guides/source/kindle/toc.html.erb b/railties/guides/source/kindle/toc.html.erb new file mode 100644 index 0000000000..e013797dee --- /dev/null +++ b/railties/guides/source/kindle/toc.html.erb @@ -0,0 +1,24 @@ +<% content_for :page_title do %> +Ruby on Rails Guides +<% end %> + +<h1>Table of Contents</h1> +<div id="toc"> + <ul><li><a href="welcome.html">Welcome</a></li></ul> +<% documents_by_section.each_with_index do |section, i| %> + <h3><%= "#{i + 1}." %> <%= section['name'] %></h3> + <ul> + <% section['documents'].each do |document| %> + <li> + <a href="<%= document['url'] %>"><%= document['name'] %></a> + <% if document['work_in_progress']%>(WIP)<% end %> + </li> + <% end %> + </ul> +<% end %> +<hr /> +<ul> + <li><a href="credits.html">Credits</a></li> + <li><a href="copyright.html">Copyright & License</a></li> +<ul> +</div> diff --git a/railties/guides/source/kindle/toc.ncx.erb b/railties/guides/source/kindle/toc.ncx.erb new file mode 100644 index 0000000000..2c6d8e3bdf --- /dev/null +++ b/railties/guides/source/kindle/toc.ncx.erb @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN" + "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd"> + +<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1" xml:lang="en-US"> +<head> + <meta name="dtb:uid" content="RailsGuides"/> + <meta name="dtb:depth" content="2"/> + <meta name="dtb:totalPageCount" content="0"/> + <meta name="dtb:maxPageNumber" content="0"/> +</head> +<docTitle><text>Ruby on Rails Guides</text></docTitle> +<docAuthor><text>docrails</text></docAuthor> +<navMap> + <navPoint playOrder="0" class="periodical" id="periodical"> + <navLabel> + <text>Table of Contents</text> + </navLabel> + <content src="toc.html"/> + + <navPoint class="section" id="welcome" playOrder="1"> + <navLabel> + <text>Introduction</text> + </navLabel> + <content src="welcome.html"/> + + <navPoint class="article" id="welcome" playOrder="2"> + <navLabel> + <text>Welcome</text> + </navLabel> + <content src="welcome.html"/> + </navPoint> + <navPoint class="article" id="credits" playOrder="3"> + <navLabel><text>Credits</text></navLabel> + <content src="credits.html"> + </navPoint> + <navPoint class="article" id="copyright" playOrder="4"> + <navLabel><text>Copyright & License</text></navLabel> + <content src="copyright.html"> + </navPoint> + </navPoint> + + <% play_order = 4 %> + <% documents_by_section.each_with_index do |section, section_no| %> + <navPoint class="section" id="chapter_<%= section_no + 1 %>" playOrder="<% play_order +=1 %>"> + <navLabel> + <text><%= section['name'] %></text> + </navLabel> + <content src="<%=section['documents'].first['url'] %>"/> + + <% section['documents'].each_with_index do |document, document_no| %> + <navPoint class="article" id="_<%=section_no+1%>.<%=document_no+1%>" playOrder="<%=play_order +=1 %>"> + <navLabel> + <text><%= document['name'] %></text> + </navLabel> + <content src="<%=document['url'] %>"/> + </navPoint> + <% end %> + </navPoint> + <% end %> + + </navPoint> +</navMap> +</ncx> diff --git a/railties/guides/source/kindle/welcome.html.erb b/railties/guides/source/kindle/welcome.html.erb new file mode 100644 index 0000000000..e30704c4e6 --- /dev/null +++ b/railties/guides/source/kindle/welcome.html.erb @@ -0,0 +1,5 @@ +<%= render 'welcome' %> + +<h3>Kindle Edition</h3> + +The Kindle Edition of the Rails Guides should be considered a work in progress. Feedback is really welcome, please see the "Feedback" section at the end of each guide for instructions. diff --git a/railties/guides/source/layout.html.erb b/railties/guides/source/layout.html.erb index 4c979888b7..e69936b43a 100644 --- a/railties/guides/source/layout.html.erb +++ b/railties/guides/source/layout.html.erb @@ -143,8 +143,7 @@ <hr class="hide" /> <div id="footer"> <div class="wrapper"> - <p>This work is licensed under a <a href="http://creativecommons.org/licenses/by-sa/3.0/">Creative Commons Attribution-Share Alike 3.0</a> License</p> - <p>"Rails", "Ruby on Rails", and the Rails logo are trademarks of David Heinemeier Hansson. All rights reserved.</p> + <%= render 'license' %> </div> </div> diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index b20634c5a9..493102a58f 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -154,15 +154,8 @@ module Rails self end - # Rails.application.env_config stores some of the Rails initial environment parameters. - # Currently stores: - # - # * action_dispatch.parameter_filter" => config.filter_parameters, - # * action_dispatch.secret_token" => config.secret_token, - # * action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions - # - # These parameters will be used by middlewares and engines to configure themselves. - # + # Stores some of the Rails initial environment parameters which + # will be used by middlewares and engines to configure themselves. def env_config @env_config ||= super.merge({ "action_dispatch.parameter_filter" => config.filter_parameters, @@ -215,6 +208,11 @@ module Rails config.helpers_paths end + def call(env) + env["ORIGINAL_FULLPATH"] = build_original_fullpath(env) + super(env) + end + protected alias :build_middleware_stack :app @@ -291,5 +289,17 @@ module Rails require "rails/console/app" require "rails/console/helpers" end + + def build_original_fullpath(env) + path_info = env["PATH_INFO"] + query_string = env["QUERY_STRING"] + script_name = env["SCRIPT_NAME"] + + if query_string.present? + "#{script_name}#{path_info}?#{query_string}" + else + "#{script_name}#{path_info}" + end + end end end diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index a782441a21..79b12ad4eb 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -12,7 +12,7 @@ module Rails :force_ssl, :helpers_paths, :logger, :log_tags, :preload_frameworks, :railties_order, :relative_url_root, :reload_plugins, :secret_token, :serve_static_assets, :ssl_options, :static_cache_control, :session_options, - :time_zone, :reload_classes_only_on_change, :whiny_nils + :time_zone, :reload_classes_only_on_change attr_writer :log_level attr_reader :encoding @@ -65,17 +65,9 @@ module Rails def encoding=(value) @encoding = value - if "ruby".encoding_aware? - silence_warnings do - Encoding.default_external = value - Encoding.default_internal = value - end - else - $KCODE = value - if $KCODE == "NONE" - raise "The value you specified for config.encoding is " \ - "invalid. The possible values are UTF8, SJIS, or EUC" - end + silence_warnings do + Encoding.default_external = value + Encoding.default_internal = value end end @@ -145,6 +137,11 @@ module Rails @session_options = args.shift || {} end end + + def whiny_nils=(*) + ActiveSupport::Deprecation.warn "config.whiny_nils option " \ + "is deprecated and no longer works", caller + end end end end diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb index 7733a8f116..3acac2a6f0 100644 --- a/railties/lib/rails/commands/console.rb +++ b/railties/lib/rails/commands/console.rb @@ -31,7 +31,7 @@ module Rails require 'ruby-debug' puts "=> Debugger enabled" rescue Exception - puts "You need to install ruby-debug to run the console in debugging mode. With gems, use 'gem install ruby-debug'" + puts "You need to install ruby-debug19 to run the console in debugging mode. With gems, use 'gem install ruby-debug19'" exit end end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 0ed1eb4af8..046b8f3925 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -135,21 +135,21 @@ module Rails if options.dev? <<-GEMFILE.strip_heredoc gem 'rails', :path => '#{Rails::Generators::RAILS_DEV_PATH}' - gem 'journey', :git => 'git://github.com/rails/journey.git' - gem 'arel', :git => 'git://github.com/rails/arel.git' + gem 'journey', :git => 'https://github.com/rails/journey.git' + gem 'arel', :git => 'https://github.com/rails/arel.git' GEMFILE elsif options.edge? <<-GEMFILE.strip_heredoc - gem 'rails', :git => 'git://github.com/rails/rails.git' - gem 'journey', :git => 'git://github.com/rails/journey.git' - gem 'arel', :git => 'git://github.com/rails/arel.git' + gem 'rails', :git => 'https://github.com/rails/rails.git' + gem 'journey', :git => 'https://github.com/rails/journey.git' + gem 'arel', :git => 'https://github.com/rails/arel.git' GEMFILE else <<-GEMFILE.strip_heredoc gem 'rails', '#{Rails::VERSION::STRING}' # Bundle edge Rails instead: - # gem 'rails', :git => 'git://github.com/rails/rails.git' + # gem 'rails', :git => 'https://github.com/rails/rails.git' GEMFILE end end @@ -193,8 +193,8 @@ module Rails # Gems used only for assets and not required # in production environments by default. group :assets do - gem 'sass-rails', :git => 'git://github.com/rails/sass-rails.git' - gem 'coffee-rails', :git => 'git://github.com/rails/coffee-rails.git' + gem 'sass-rails', :git => 'https://github.com/rails/sass-rails.git' + gem 'coffee-rails', :git => 'https://github.com/rails/coffee-rails.git' #{"gem 'therubyrhino'\n" if defined?(JRUBY_VERSION)} gem 'uglifier', '>= 1.0.3' end @@ -204,8 +204,8 @@ module Rails # Gems used only for assets and not required # in production environments by default. group :assets do - gem 'sass-rails', '~> 3.2.0' - gem 'coffee-rails', '~> 3.2.0' + gem 'sass-rails', '~> 4.0.0.beta' + gem 'coffee-rails', '~> 4.0.0.beta' #{"gem 'therubyrhino'\n" if defined?(JRUBY_VERSION)} gem 'uglifier', '>= 1.0.3' end diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb index f38a487a4e..af743a9c51 100644 --- a/railties/lib/rails/generators/base.rb +++ b/railties/lib/rails/generators/base.rb @@ -128,13 +128,13 @@ module Rails # # ==== Boolean hooks # - # In some cases, you want to provide a boolean hook. For example, webrat + # In some cases, you may want to provide a boolean hook. For example, webrat # developers might want to have webrat available on controller generator. # This can be achieved as: # # Rails::Generators::ControllerGenerator.hook_for :webrat, :type => :boolean # - # Then, if you want, webrat to be invoked, just supply: + # Then, if you want webrat to be invoked, just supply: # # rails generate controller Account --webrat # @@ -146,7 +146,7 @@ module Rails # # You can also supply a block to hook_for to customize how the hook is # going to be invoked. The block receives two arguments, an instance - # of the current class and the klass to be invoked. + # of the current class and the class to be invoked. # # For example, in the resource generator, the controller should be invoked # with a pluralized class name. But by default it is invoked with the same diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb index 816d82cac3..61479b9068 100644 --- a/railties/lib/rails/generators/generated_attribute.rb +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -1,14 +1,48 @@ require 'active_support/time' require 'active_support/core_ext/object/inclusion' +require 'active_support/core_ext/object/blank' module Rails module Generators class GeneratedAttribute attr_accessor :name, :type + attr_reader :attr_options - def initialize(name, type) - type = :string if type.blank? - @name, @type = name, type.to_sym + class << self + def parse(column_definition) + name, type, has_index = column_definition.split(':') + + # if user provided "name:index" instead of "name:string:index" + # type should be set blank so GeneratedAttribute's constructor + # could set it to :string + has_index, type = type, nil if %w(index uniq).include?(type) + + type, attr_options = *parse_type_and_options(type) + new(name, type, has_index, attr_options) + end + + private + + # parse possible attribute options like :limit for string/text/binary/integer or :precision/:scale for decimals + # when declaring options curly brackets should be used + def parse_type_and_options(type) + case type + when /(string|text|binary|integer)\{(\d+)\}/ + return $1, :limit => $2.to_i + when /decimal\{(\d+),(\d+)\}/ + return :decimal, :precision => $1.to_i, :scale => $2.to_i + else + return type, {} + end + end + end + + def initialize(name, type=nil, index_type=false, attr_options={}) + @name = name + @type = (type.presence || :string).to_sym + @has_index = %w(index uniq).include?(index_type) + @has_uniq_index = %w(uniq).include?(index_type) + @attr_options = attr_options end def field_type @@ -45,9 +79,29 @@ module Rails name.to_s.humanize end + def index_name + reference? ? "#{name}_id" : name + end + def reference? self.type.in?([:references, :belongs_to]) end + + def has_index? + @has_index + end + + def has_uniq_index? + @has_uniq_index + end + + def inject_options + "".tap { |s| @attr_options.each { |k,v| s << ", #{k}: #{v.inspect}" } } + end + + def inject_index_options + has_uniq_index? ? ", unique: true" : "" + end end end end diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index e96fc63ee9..9cef55e0a6 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -150,9 +150,8 @@ module Rails # Convert attributes array into GeneratedAttribute objects. def parse_attributes! #:nodoc: - self.attributes = (attributes || []).map do |key_value| - name, type = key_value.split(':') - Rails::Generators::GeneratedAttribute.new(name, type) + self.attributes = (attributes || []).map do |attr| + Rails::Generators::GeneratedAttribute.parse(attr) end end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index 9e53b6a70d..a1d41fd7dd 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -6,9 +6,6 @@ # since you don't have to restart the web server when you make code changes. config.cache_classes = false - # Log error messages when you accidentally call methods on nil. - config.whiny_nils = true - # Show full error reports and disable caching config.consider_all_requests_local = true config.action_controller.perform_caching = false diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt index 37a8b81dad..86016da189 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt @@ -11,9 +11,6 @@ config.serve_static_assets = true config.static_cache_control = "public, max-age=3600" - # Log error messages when you accidentally call methods on nil - config.whiny_nils = true - # Show full error reports and disable caching config.consider_all_requests_local = true config.action_controller.perform_caching = false diff --git a/railties/lib/rails/generators/rails/migration/migration_generator.rb b/railties/lib/rails/generators/rails/migration/migration_generator.rb index 39fa5b63b1..f87dce1502 100644 --- a/railties/lib/rails/generators/rails/migration/migration_generator.rb +++ b/railties/lib/rails/generators/rails/migration/migration_generator.rb @@ -1,7 +1,7 @@ module Rails module Generators class MigrationGenerator < NamedBase #metagenerator - argument :attributes, :type => :array, :default => [], :banner => "field:type field:type" + argument :attributes, :type => :array, :default => [], :banner => "field[:type][:index] field[:type][:index]" hook_for :orm, :required => true end end diff --git a/railties/lib/rails/generators/rails/model/model_generator.rb b/railties/lib/rails/generators/rails/model/model_generator.rb index 629d5eed3f..9bb29b784e 100644 --- a/railties/lib/rails/generators/rails/model/model_generator.rb +++ b/railties/lib/rails/generators/rails/model/model_generator.rb @@ -1,7 +1,7 @@ module Rails module Generators class ModelGenerator < NamedBase #metagenerator - argument :attributes, :type => :array, :default => [], :banner => "field:type field:type" + argument :attributes, :type => :array, :default => [], :banner => "field[:type][:index] field[:type][:index]" hook_for :orm, :required => true end end diff --git a/railties/lib/rails/generators/rails/scaffold/USAGE b/railties/lib/rails/generators/rails/scaffold/USAGE index be1d113ed8..4a3eb2c7c7 100644 --- a/railties/lib/rails/generators/rails/scaffold/USAGE +++ b/railties/lib/rails/generators/rails/scaffold/USAGE @@ -7,23 +7,29 @@ Description: under_scored, as the first argument, and an optional list of attribute pairs. - Attribute pairs are field:type arguments specifying the - model's attributes. Timestamps are added by default, so you don't have to - specify them by hand as 'created_at:datetime updated_at:datetime'. + Attributes are field arguments specifying the model's attributes. You can + optionally pass the type and an index to each field. For instance: + "title body:text tracking_id:integer:uniq" will generate a title field of + string type, a body with text type and a tracking_id as an integer with an + unique index. "index" could also be given instead of "uniq" if one desires + a non unique index. + + Timestamps are added by default, so you don't have to specify them by hand + as 'created_at:datetime updated_at:datetime'. You don't have to think up every attribute up front, but it helps to sketch out a few so you can start working with the resource immediately. - For example, 'scaffold post title:string body:text published:boolean' - gives you a model with those three attributes, a controller that handles + For example, 'scaffold post title body:text published:boolean' gives + you a model with those three attributes, a controller that handles the create/show/update/destroy, forms to create and edit your posts, and - an index that lists them all, as well as a resources :posts - declaration in config/routes.rb. + an index that lists them all, as well as a resources :posts declaration + in config/routes.rb. If you want to remove all the generated files, run 'rails destroy scaffold ModelName'. Examples: `rails generate scaffold post` - `rails generate scaffold post title:string body:text published:boolean` - `rails generate scaffold purchase order_id:integer amount:decimal` + `rails generate scaffold post title body:text published:boolean` + `rails generate scaffold purchase amount:decimal tracking_id:integer:uniq` diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb index 7319fb79f6..d81c4c3e1d 100644 --- a/railties/lib/rails/generators/test_case.rb +++ b/railties/lib/rails/generators/test_case.rb @@ -218,8 +218,8 @@ module Rails # # create_generated_attribute(:string, 'name') # - def create_generated_attribute(attribute_type, name = 'test') - Rails::Generators::GeneratedAttribute.new(name, attribute_type.to_s) + def create_generated_attribute(attribute_type, name = 'test', index = nil) + Rails::Generators::GeneratedAttribute.parse([name, attribute_type, index].compact.join(':')) end protected diff --git a/railties/lib/rails/test_unit/sub_test_task.rb b/railties/lib/rails/test_unit/sub_test_task.rb new file mode 100644 index 0000000000..284c70050f --- /dev/null +++ b/railties/lib/rails/test_unit/sub_test_task.rb @@ -0,0 +1,36 @@ +module Rails + # Don't abort when tests fail; move on the next test task. + # Silence the default description to cut down on `rake -T` noise. + class SubTestTask < Rake::TestTask + # Create the tasks defined by this task lib. + def define + lib_path = @libs.join(File::PATH_SEPARATOR) + task @name do + run_code = '' + RakeFileUtils.verbose(@verbose) do + run_code = + case @loader + when :direct + "-e 'ARGV.each{|f| load f}'" + when :testrb + "-S testrb #{fix}" + when :rake + rake_loader + end + @ruby_opts.unshift( "-I\"#{lib_path}\"" ) + @ruby_opts.unshift( "-w" ) if @warning + + begin + ruby @ruby_opts.join(" ") + + " \"#{run_code}\" " + + file_list.collect { |fn| "\"#{fn}\"" }.join(' ') + + " #{option_list}" + rescue => error + warn "Error running #{name}: #{error.inspect}" + end + end + end + self + end + end +end diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake index 52d92cdd96..a23d22d607 100644 --- a/railties/lib/rails/test_unit/testing.rake +++ b/railties/lib/rails/test_unit/testing.rake @@ -1,35 +1,6 @@ require 'rbconfig' require 'rake/testtask' - -# Monkey-patch to silence the description from Rake::TestTask to cut down on rake -T noise -class TestTaskWithoutDescription < Rake::TestTask - # Create the tasks defined by this task lib. - def define - lib_path = @libs.join(File::PATH_SEPARATOR) - task @name do - run_code = '' - RakeFileUtils.verbose(@verbose) do - run_code = - case @loader - when :direct - "-e 'ARGV.each{|f| load f}'" - when :testrb - "-S testrb #{fix}" - when :rake - rake_loader - end - @ruby_opts.unshift( "-I\"#{lib_path}\"" ) - @ruby_opts.unshift( "-w" ) if @warning - ruby @ruby_opts.join(" ") + - " \"#{run_code}\" " + - file_list.collect { |fn| "\"#{fn}\"" }.join(' ') + - " #{option_list}" - end - end - self - end -end - +require 'rails/test_unit/sub_test_task' TEST_CHANGES_SINCE = Time.now - 600 @@ -76,20 +47,7 @@ task :default => :test desc 'Runs test:units, test:functionals, test:integration together (also available: test:benchmark, test:profile, test:plugins)' task :test do - tests_to_run = ENV['TEST'] ? ["test:single"] : %w(test:units test:functionals test:integration) - errors = tests_to_run.collect do |task| - begin - Rake::Task[task].invoke - nil - rescue => e - { :task => task, :exception => e } - end - end.compact - - if errors.any? - puts errors.map { |e| "Errors running #{e[:task]}! #{e[:exception].inspect}" }.join("\n") - abort - end + Rake::Task[ENV['TEST'] ? 'test:single' : 'test:run'].invoke end namespace :test do @@ -97,6 +55,8 @@ namespace :test do # Placeholder task for other Railtie and plugins to enhance. See Active Record for an example. end + task :run => %w(test:units test:functionals test:integration) + Rake::TestTask.new(:recent => "test:prepare") do |t| since = TEST_CHANGES_SINCE touched = FileList['test/**/*_test.rb'].select { |path| File.mtime(path) > since } + @@ -134,33 +94,33 @@ namespace :test do t.libs << "test" end - TestTaskWithoutDescription.new(:units => "test:prepare") do |t| + Rails::SubTestTask.new(:units => "test:prepare") do |t| t.libs << "test" t.pattern = 'test/unit/**/*_test.rb' end - TestTaskWithoutDescription.new(:functionals => "test:prepare") do |t| + Rails::SubTestTask.new(:functionals => "test:prepare") do |t| t.libs << "test" t.pattern = 'test/functional/**/*_test.rb' end - TestTaskWithoutDescription.new(:integration => "test:prepare") do |t| + Rails::SubTestTask.new(:integration => "test:prepare") do |t| t.libs << "test" t.pattern = 'test/integration/**/*_test.rb' end - TestTaskWithoutDescription.new(:benchmark => 'test:prepare') do |t| + Rails::SubTestTask.new(:benchmark => 'test:prepare') do |t| t.libs << 'test' t.pattern = 'test/performance/**/*_test.rb' t.options = '-- --benchmark' end - TestTaskWithoutDescription.new(:profile => 'test:prepare') do |t| + Rails::SubTestTask.new(:profile => 'test:prepare') do |t| t.libs << 'test' t.pattern = 'test/performance/**/*_test.rb' end - TestTaskWithoutDescription.new(:plugins => :environment) do |t| + Rails::SubTestTask.new(:plugins => :environment) do |t| t.libs << "test" if ENV['PLUGIN'] diff --git a/railties/test/application/build_original_fullpath_test.rb b/railties/test/application/build_original_fullpath_test.rb new file mode 100644 index 0000000000..7a679ea04e --- /dev/null +++ b/railties/test/application/build_original_fullpath_test.rb @@ -0,0 +1,27 @@ +require "abstract_unit" + +module ApplicationTests + class BuildOriginalPathTest < Test::Unit::TestCase + def test_include_original_PATH_info_in_ORIGINAL_FULLPATH + env = { 'PATH_INFO' => '/foo/' } + assert_equal "/foo/", Rails.application.send(:build_original_fullpath, env) + end + + def test_include_SCRIPT_NAME + env = { + 'SCRIPT_NAME' => '/foo', + 'PATH_INFO' => '/bar' + } + + assert_equal "/foo/bar", Rails.application.send(:build_original_fullpath, env) + end + + def test_include_QUERY_STRING + env = { + 'PATH_INFO' => '/foo', + 'QUERY_STRING' => 'bar', + } + assert_equal "/foo?bar", Rails.application.send(:build_original_fullpath, env) + end + end +end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 8f2e6e9ac0..0d64a136f8 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -1,4 +1,5 @@ require "isolation/abstract_unit" +require 'rack/test' class ::MyMailInterceptor def self.delivering_email(email); email; end @@ -15,6 +16,7 @@ class ::MyOtherMailObserver < ::MyMailObserver; end module ApplicationTests class ConfigurationTest < Test::Unit::TestCase include ActiveSupport::Testing::Isolation + include Rack::Test::Methods def new_app File.expand_path("#{app_path}/../new_app") @@ -181,8 +183,6 @@ module ApplicationTests assert !$prepared require "#{app_path}/config/environment" - require 'rack/test' - extend Rack::Test::Methods get "/" assert $prepared @@ -282,6 +282,11 @@ module ApplicationTests assert_equal res, last_response.body # value should be unchanged end + test "sets ActionDispatch.test_app" do + make_basic_app + assert_equal Rails.application, ActionDispatch.test_app + end + test "sets all Active Record models to whitelist all attributes by default" do add_to_config <<-RUBY config.active_record.whitelist_attributes = true @@ -479,14 +484,12 @@ module ApplicationTests RUBY add_to_config <<-RUBY - routes.append do + routes.prepend do resources :posts end RUBY require "#{app_path}/config/environment" - require "rack/test" - extend Rack::Test::Methods post "/posts.json", '{ "title": "foo", "name": "bar" }', "CONTENT_TYPE" => "application/json" assert_equal '{"title"=>"foo"}', last_response.body diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index cf6c4d8fc2..a0417360a1 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -137,7 +137,7 @@ module ApplicationTests end test "assignment config.encoding to default_charset" do - charset = "ruby".respond_to?(:force_encoding) ? 'Shift_JIS' : 'UTF8' + charset = 'Shift_JIS' add_to_config "config.encoding = '#{charset}'" require "#{app_path}/config/environment" assert_equal charset, ActionDispatch::Response.default_charset diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index 578370cfca..9e02ef9c66 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -1,5 +1,6 @@ require 'isolation/abstract_unit' require 'stringio' +require 'rack/test' module ApplicationTests class MiddlewareTest < Test::Unit::TestCase @@ -75,7 +76,7 @@ module ApplicationTests add_to_config "config.force_ssl = true" add_to_config "config.ssl_options = { :host => 'example.com' }" boot! - + assert_equal AppTemplate::Application.middleware.first.args, [{:host => 'example.com'}] end @@ -193,6 +194,14 @@ module ApplicationTests assert_equal nil, last_response.headers["Etag"] end + test "ORIGINAL_FULLPATH is passed to env" do + boot! + env = ::Rack::MockRequest.env_for("/foo/?something") + Rails.application.call(env) + + assert_equal "/foo/?something", env["ORIGINAL_FULLPATH"] + end + private def boot! diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index d4d4e4e5ff..1d90671e44 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -63,26 +63,23 @@ module ApplicationTests def test_rake_test_error_output Dir.chdir(app_path){ `rake db:migrate` } - app_file "config/database.yml", <<-RUBY - development: - RUBY - app_file "test/unit/one_unit_test.rb", <<-RUBY + raise 'unit' RUBY app_file "test/functional/one_functional_test.rb", <<-RUBY - raise RuntimeError + raise 'functional' RUBY app_file "test/integration/one_integration_test.rb", <<-RUBY - raise RuntimeError + raise 'integration' RUBY silence_stderr do - output = Dir.chdir(app_path){ `rake test` } - assert_match(/Errors running test:units! #<ActiveRecord::AdapterNotSpecified/, output) - assert_match(/Errors running test:functionals! #<RuntimeError/, output) - assert_match(/Errors running test:integration! #<RuntimeError/, output) + output = Dir.chdir(app_path) { `rake test 2>&1` } + assert_match 'unit', output + assert_match 'functional', output + assert_match 'integration', output end end diff --git a/railties/test/generators/generated_attribute_test.rb b/railties/test/generators/generated_attribute_test.rb index a85829085c..6e3fc84781 100644 --- a/railties/test/generators/generated_attribute_test.rb +++ b/railties/test/generators/generated_attribute_test.rb @@ -69,7 +69,7 @@ class GeneratedAttributeTest < Rails::Generators::TestCase end def test_default_value_for_type - att = Rails::Generators::GeneratedAttribute.new("type", "string") + att = Rails::Generators::GeneratedAttribute.parse("type:string") assert_equal("", att.default) end @@ -122,4 +122,9 @@ class GeneratedAttributeTest < Rails::Generators::TestCase assert_equal :string, create_generated_attribute(nil, 'title').type assert_equal :string, create_generated_attribute("", 'title').type end + + def test_handles_index_names_for_references + assert_equal "post", create_generated_attribute('string', 'post').index_name + assert_equal "post_id", create_generated_attribute('references', 'post').index_name + end end diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb index 337257df7d..68fbd58061 100644 --- a/railties/test/generators/migration_generator_test.rb +++ b/railties/test/generators/migration_generator_test.rb @@ -58,6 +58,68 @@ class MigrationGeneratorTest < Rails::Generators::TestCase end end + def test_add_migration_with_attributes_and_indices + migration = "add_title_with_index_and_body_to_posts" + run_generator [migration, "title:string:index", "body:text", "user_id:integer:uniq"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |up| + assert_match(/add_column :posts, :title, :string/, up) + assert_match(/add_column :posts, :body, :text/, up) + assert_match(/add_column :posts, :user_id, :integer/, up) + end + assert_match(/add_index :posts, :title/, content) + assert_match(/add_index :posts, :user_id, unique: true/, content) + end + end + + def test_add_migration_with_attributes_and_wrong_index_declaration + migration = "add_title_and_content_to_books" + run_generator [migration, "title:string:inex", "content:text", "user_id:integer:unik"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |up| + assert_match(/add_column :books, :title, :string/, up) + assert_match(/add_column :books, :content, :text/, up) + assert_match(/add_column :books, :user_id, :integer/, up) + end + assert_no_match(/add_index :books, :title/, content) + assert_no_match(/add_index :books, :user_id/, content) + end + end + + def test_add_migration_with_attributes_without_type_and_index + migration = "add_title_with_index_and_body_to_posts" + run_generator [migration, "title:index", "body:text", "user_uuid:uniq"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |up| + assert_match(/add_column :posts, :title, :string/, up) + assert_match(/add_column :posts, :body, :text/, up) + assert_match(/add_column :posts, :user_uuid, :string/, up) + end + assert_match(/add_index :posts, :title/, content) + assert_match(/add_index :posts, :user_uuid, unique: true/, content) + end + end + + def test_add_migration_with_attributes_index_declaration_and_attribute_options + migration = "add_title_and_content_to_books" + run_generator [migration, "title:string{40}:index", "content:string{255}", "price:decimal{5,2}:index", "discount:decimal{3,2}:uniq"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |up| + assert_match(/add_column :books, :title, :string, limit: 40/, up) + assert_match(/add_column :books, :content, :string, limit: 255/, up) + assert_match(/add_column :books, :price, :decimal, precision: 5, scale: 2/, up) + assert_match(/add_column :books, :discount, :decimal, precision: 3, scale: 2/, up) + end + assert_match(/add_index :books, :title/, content) + assert_match(/add_index :books, :price/, content) + assert_match(/add_index :books, :discount, unique: true/, content) + end + end + def test_should_create_empty_migrations_if_name_not_start_with_add_or_remove migration = "create_books" run_generator [migration, "title:string", "content:text"] diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index 1b0cb425c6..156fa86eee 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -113,6 +113,74 @@ class ModelGeneratorTest < Rails::Generators::TestCase end end + def test_migration_with_attributes_and_with_index + run_generator ["product", "name:string:index", "supplier_id:integer:index", "user_id:integer:uniq", "order_id:uniq"] + + assert_migration "db/migrate/create_products.rb" do |m| + assert_method :change, m do |up| + assert_match(/create_table :products/, up) + assert_match(/t\.string :name/, up) + assert_match(/t\.integer :supplier_id/, up) + assert_match(/t\.integer :user_id/, up) + assert_match(/t\.string :order_id/, up) + + assert_match(/add_index :products, :name/, up) + assert_match(/add_index :products, :supplier_id/, up) + assert_match(/add_index :products, :user_id, unique: true/, up) + assert_match(/add_index :products, :order_id, unique: true/, up) + end + end + end + + def test_migration_with_attributes_and_with_wrong_index_declaration + run_generator ["product", "name:string", "supplier_id:integer:inex", "user_id:integer:unqu"] + + assert_migration "db/migrate/create_products.rb" do |m| + assert_method :change, m do |up| + assert_match(/create_table :products/, up) + assert_match(/t\.string :name/, up) + assert_match(/t\.integer :supplier_id/, up) + assert_match(/t\.integer :user_id/, up) + + assert_no_match(/add_index :products, :name/, up) + assert_no_match(/add_index :products, :supplier_id/, up) + assert_no_match(/add_index :products, :user_id/, up) + end + end + end + + def test_migration_with_missing_attribute_type_and_with_index + run_generator ["product", "name:index", "supplier_id:integer:index", "year:integer"] + + assert_migration "db/migrate/create_products.rb" do |m| + assert_method :change, m do |up| + assert_match(/create_table :products/, up) + assert_match(/t\.string :name/, up) + assert_match(/t\.integer :supplier_id/, up) + + assert_match(/add_index :products, :name/, up) + assert_match(/add_index :products, :supplier_id/, up) + assert_no_match(/add_index :products, :year/, up) + end + end + end + + def test_add_migration_with_attributes_index_declaration_and_attribute_options + run_generator ["product", "title:string{40}:index", "content:string{255}", "price:decimal{5,2}:index", "discount:decimal{5,2}:uniq"] + + assert_migration "db/migrate/create_products.rb" do |content| + assert_method :change, content do |up| + assert_match(/create_table :products/, up) + assert_match(/t.string :title, limit: 40/, up) + assert_match(/t.string :content, limit: 255/, up) + assert_match(/t.decimal :price, precision: 5, scale: 2/, up) + end + assert_match(/add_index :products, :title/, content) + assert_match(/add_index :products, :price/, content) + assert_match(/add_index :products, :discount, unique: true/, content) + end + end + def test_migration_without_timestamps ActiveRecord::Base.timestamped_migrations = false run_generator ["account"] diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb index 1534f0d828..14a20eddb8 100644 --- a/railties/test/generators/shared_generator_tests.rb +++ b/railties/test/generators/shared_generator_tests.rb @@ -125,7 +125,7 @@ module SharedGeneratorTests def test_edge_option generator([destination_root], :edge => true).expects(:bundle_command).with('install').once quietly { generator.invoke_all } - assert_file 'Gemfile', %r{^gem\s+["']rails["'],\s+:git\s+=>\s+["']#{Regexp.escape("git://github.com/rails/rails.git")}["']$} + assert_file 'Gemfile', %r{^gem\s+["']rails["'],\s+:git\s+=>\s+["']#{Regexp.escape("https://github.com/rails/rails.git")}["']$} end def test_skip_gemfile |