diff options
219 files changed, 2873 insertions, 1326 deletions
@@ -10,7 +10,7 @@ end gem "coffee-script" gem "sass" -gem "uglifier" +gem "uglifier", :git => 'git://github.com/lautis/uglifier.git' gem "rake", ">= 0.8.7" gem "mocha", ">= 0.9.8" @@ -27,7 +27,6 @@ gem "memcache-client", ">= 1.8.5" platforms :mri_18 do gem "system_timer" gem "ruby-debug", ">= 0.10.3" - gem 'ruby-prof' gem "json" end @@ -44,6 +43,9 @@ platforms :ruby do gem 'yajl-ruby' gem "nokogiri", ">= 1.4.4" + group :test do + gem 'ruby-prof' + end # AR gem "sqlite3", "~> 1.3.3" diff --git a/RAILS_VERSION b/RAILS_VERSION index 30afce968e..b4e716a7c1 100644 --- a/RAILS_VERSION +++ b/RAILS_VERSION @@ -1 +1 @@ -3.1.0.beta +3.1.0.beta1 diff --git a/actionmailer/CHANGELOG b/actionmailer/CHANGELOG index 167c1da9c5..d4475bc951 100644 --- a/actionmailer/CHANGELOG +++ b/actionmailer/CHANGELOG @@ -2,14 +2,42 @@ * No changes -*Rails 3.0.2 (unreleased)* + +*Rails 3.0.7 (April 18, 2011)* + +* remove AM delegating register_observer and register_interceptor to Mail [Josh Kalderimis] + + +*Rails 3.0.6 (April 5, 2011) + +* Don't allow i18n to change the minor version, version now set to ~> 0.5.0 [Santiago Pastorino] + + +*Rails 3.0.5 (February 26, 2011)* + +* No changes. + + +*Rails 3.0.4 (February 8, 2011)* + +* No changes. + + +*Rails 3.0.3 (November 16, 2010)* + +* No changes. + + +*Rails 3.0.2 (November 15, 2010)* * No changes + *Rails 3.0.1 (October 15, 2010)* * No Changes, just a version bump. + *Rails 3.0.0 (August 29, 2010)* * subject is automatically looked up on I18n using mailer_name and action_name as scope as in t(".subject") [JK] diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc index 9b206fbcc7..af9bf40f9e 100644 --- a/actionmailer/README.rdoc +++ b/actionmailer/README.rdoc @@ -157,5 +157,5 @@ API documentation is at Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here: -* https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets +* https://github.com/rails/rails/issues diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 1e91d62e24..f00a0c8ae0 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -349,8 +349,7 @@ module ActionMailer #:nodoc: include AbstractController::Translation include AbstractController::AssetPaths - cattr_reader :protected_instance_variables - @@protected_instance_variables = [] + self.protected_instance_variables = %w(@_action_has_layout) helper ActionMailer::MailHelper include ActionMailer::OldApi diff --git a/actionmailer/lib/action_mailer/old_api.rb b/actionmailer/lib/action_mailer/old_api.rb index 04728cafb0..bfa9499764 100644 --- a/actionmailer/lib/action_mailer/old_api.rb +++ b/actionmailer/lib/action_mailer/old_api.rb @@ -8,7 +8,7 @@ module ActionMailer included do extend ActionMailer::AdvAttrAccessor - self.protected_instance_variables.concat %w(@parts @mail_was_called) + self.protected_instance_variables.concat %w(@parts @mail_was_called @headers) # Specify the BCC addresses for the message adv_attr_accessor :bcc diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb index 27eb6c2b9b..8cf3780fbc 100644 --- a/actionmailer/lib/action_mailer/version.rb +++ b/actionmailer/lib/action_mailer/version.rb @@ -3,7 +3,7 @@ module ActionMailer MAJOR = 3 MINOR = 1 TINY = 0 - PRE = "beta" + PRE = "beta1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb index 370a508cad..88b074cef5 100644 --- a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb +++ b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb @@ -1,6 +1,6 @@ <% module_namespacing do -%> class <%= class_name %> < ActionMailer::Base - default :from => "from@example.com" + default <%= key_value :from, '"from@example.com"' %> <% for action in actions -%> # Subject can be set in your I18n file at config/locales/en.yml @@ -11,7 +11,7 @@ class <%= class_name %> < ActionMailer::Base def <%= action %> @greeting = "Hi" - mail :to => "to@example.org" + mail <%= key_value :to, '"to@example.org"' %> end <% end -%> end diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index ba01c4749f..68076b794e 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,11 @@ *Rails 3.1.0 (unreleased)* +* Warn if we cannot verify CSRF token authenticity [José Valim] + +* Allow AM/PM format in datetime selectors [Aditya Sanghi] + +* Only show dump of regular env methods on exception screen (not all the rack crap) [DHH] + * auto_link has been removed with no replacement. If you still use auto_link please install the rails_autolink gem: http://github.com/tenderlove/rails_autolink @@ -112,8 +118,6 @@ tested. Keys are dasherized. Values are JSON-encoded, except for strings and symbols. [Stephen Celis] -* Added render :once. You can pass either a string or an array of strings and Rails will ensure they each of them are rendered just once. [José Valim] - * Deprecate old template handler API. The new API simply requires a template handler to respond to call. [José Valim] * :rhtml and :rxml were finally removed as template handlers. [José Valim] @@ -129,7 +133,58 @@ tested. * Add Rack::Cache to the default stack. Create a Rails store that delegates to the Rails cache, so by default, whatever caching layer you are using will be used for HTTP caching. Note that Rack::Cache will be used if you use #expires_in, #fresh_when or #stale with :public => true. Otherwise, the caching rules will apply to the browser only. [Yehuda Katz, Carl Lerche] -*Rails 3.0.2 (unreleased)* +*Rails 3.0.7 (April 18, 2011)* + +*No changes. + + +*Rails 3.0.6 (April 5, 2011) + +* Fixed XSS vulnerability in `auto_link`. `auto_link` no longer marks input as + html safe. Please make sure that calls to auto_link() are wrapped in a + sanitize(), or a raw() depending on the type of input passed to auto_link(). + For example: + + <%= sanitize(auto_link(some_user_input)) %> + + Thanks to Torben Schulz for reporting this. The fix can be found here: + 61ee3449674c591747db95f9b3472c5c3bd9e84d + +* Fixes the output of `rake routes` to be correctly match to the behavior of the application, as the regular expression used to match the path is greedy and won't capture the format part by default [Prem Sichanugrist] + +* Fixes an issue with number_to_human when converting values which are less than 1 but greater than -1 [Josh Kalderimis] + +* Sensitive query string parameters (specified in config.filter_parameters) will now be filtered out from the request paths in the log file. [Prem Sichanugrist, fxn] + +* URL parameters which return nil for to_param are now removed from the query string [Andrew White] + +* Don't allow i18n to change the minor version, version now set to ~> 0.5.0 [Santiago Pastorino] + +* Make TranslationHelper#translate use the :rescue_format option in I18n 0.5.0 [Sven Fuchs] + +* Fix regression: javascript_include_tag shouldn't raise if you register an expansion key with nil or [] value [Santiago Pastorino] + +* Fix Action caching bug where an action that has a non-cacheable response always renders a nil response body. It now correctly renders the response body. [Cheah Chu Yeow] + + +*Rails 3.0.5 (February 26, 2011)* + +* No changes. + + +*Rails 3.0.4 (February 8, 2011)* + +* No changes. + + +*Rails 3.0.3 (November 16, 2010)* + +* When ActiveRecord::Base objects are sent to predicate methods, the id of the object should be sent to ARel, not the ActiveRecord::Base object. + +* :constraints routing should only do sanity checks against regular expressions. String arguments are OK. + + +*Rails 3.0.2 (November 15, 2010)* * The helper number_to_currency accepts a new :negative_format option to be able to configure how to render negative amounts. [Don Wilson] diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc index 1a56c44db5..5db4cff66b 100644 --- a/actionpack/README.rdoc +++ b/actionpack/README.rdoc @@ -338,4 +338,4 @@ API documentation is at Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here: -* https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets +* https://github.com/rails/rails/issues diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 4aa2c894f9..606abac83a 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -19,13 +19,13 @@ Gem::Specification.new do |s| s.add_dependency('activesupport', version) s.add_dependency('activemodel', version) - s.add_dependency('rack-cache', '~> 1.0.0') + s.add_dependency('rack-cache', '~> 1.0.1') s.add_dependency('builder', '~> 3.0.0') s.add_dependency('i18n', '~> 0.6.0beta1') s.add_dependency('rack', '~> 1.3.0.beta') s.add_dependency('rack-test', '~> 0.6.0') - s.add_dependency('rack-mount', '~> 0.7.2') + s.add_dependency('rack-mount', '~> 0.8.0') s.add_dependency('sprockets', '~> 2.0.0.beta.2') - s.add_dependency('tzinfo', '~> 0.3.23') + s.add_dependency('tzinfo', '~> 0.3.27') s.add_dependency('erubis', '~> 2.7.0') end diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index 0951267fea..f67d0e558e 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -130,27 +130,39 @@ module AbstractController self.class.action_methods end - # Returns true if the name can be considered an action. This can - # be overridden in subclasses to modify the semantics of what - # can be considered an action. + # Returns true if a method for the action is available and + # can be dispatched, false otherwise. # - # For instance, this is overriden by ActionController to add - # the implicit rendering feature. - # - # ==== Parameters - # * <tt>name</tt> - The name of an action to be tested - # - # ==== Returns - # * <tt>TrueClass</tt>, <tt>FalseClass</tt> - def action_method?(name) - self.class.action_methods.include?(name) + # Notice that <tt>action_methods.include?("foo")</tt> may return + # false and <tt>available_action?("foo")</tt> returns true because + # available action consider actions that are also available + # through other means, for example, implicit render ones. + def available_action?(action_name) + method_for_action(action_name).present? end private + # Returns true if the name can be considered an action because + # it has a method defined in the controller. + # + # ==== Parameters + # * <tt>name</tt> - The name of an action to be tested + # + # ==== Returns + # * <tt>TrueClass</tt>, <tt>FalseClass</tt> + # + # :api: private + def action_method?(name) + self.class.action_methods.include?(name) + end + # Call the action. Override this in a subclass to modify the # behavior around processing an action. This, and not #process, # is the intended way to override action dispatching. + # + # Notice that the first argument is the method to be dispatched + # which is *not* necessarily the same as the action name. def process_action(method_name, *args) send_action(method_name, *args) end diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index f7b2b7ff53..e8426bc52b 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -13,8 +13,8 @@ module AbstractController # Override AbstractController::Base's process_action to run the # process_action callbacks around the normal behavior. - def process_action(method_name, *args) - run_callbacks(:process_action, method_name) do + def process_action(*args) + run_callbacks(:process_action, action_name) do super end end diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index d1b87b67ee..8f73e244d7 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -292,15 +292,15 @@ module AbstractController end end - attr_writer :action_has_layout + attr_internal_writer :action_has_layout def initialize(*) - @action_has_layout = true + @_action_has_layout = true super end def action_has_layout? - @action_has_layout + @_action_has_layout end private diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index f78365afdb..ab2c532859 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -32,9 +32,13 @@ module AbstractController module Rendering extend ActiveSupport::Concern - include AbstractController::ViewPaths + included do + config_accessor :protected_instance_variables, :instance_reader => false + self.protected_instance_variables = [] + end + # Overwrite process to setup I18n proxy. def process(*) #:nodoc: old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context) @@ -53,14 +57,20 @@ module AbstractController end end - attr_writer :view_context_class + 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 + @_view_context_class || self.class.view_context_class end def initialize(*) - @view_context_class = nil + @_view_context_class = nil super end @@ -79,7 +89,7 @@ module AbstractController # Returns an object that is able to render templates. def view_renderer - @view_renderer ||= ActionView::Renderer.new(lookup_context) + @_view_renderer ||= ActionView::Renderer.new(lookup_context) end # Normalize arguments, options and then delegates render_to_body and @@ -112,13 +122,19 @@ module AbstractController private + DEFAULT_PROTECTED_INSTANCE_VARIABLES = %w( + @_action_name @_response_body @_formats @_prefixes @_config + @_view_context_class @_view_renderer @_lookup_context + ) + # This method should return a hash with assigns. # You can overwrite this configuration per controller. # :api: public def view_assigns hash = {} variables = instance_variable_names - variables -= protected_instance_variables if respond_to?(:protected_instance_variables) + variables -= protected_instance_variables + variables -= DEFAULT_PROTECTED_INSTANCE_VARIABLES variables.each { |name| hash[name.to_s[1, name.length]] = instance_variable_get(name) } hash end diff --git a/actionpack/lib/abstract_controller/view_paths.rb b/actionpack/lib/abstract_controller/view_paths.rb index 0893459e24..6b7aae8c74 100644 --- a/actionpack/lib/abstract_controller/view_paths.rb +++ b/actionpack/lib/abstract_controller/view_paths.rb @@ -39,7 +39,7 @@ module AbstractController # templates, i.e. view paths and details. Check ActionView::LookupContext for more # information. def lookup_context - @lookup_context ||= + @_lookup_context ||= ActionView::LookupContext.new(self.class._view_paths, details_for_lookup, _prefixes) end diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index 3fae697cc3..8d813a8e38 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -7,8 +7,10 @@ module ActionController def start_processing(event) payload = event.payload params = payload[:params].except(*INTERNAL_PARAMS) + format = payload[:format] + format = format.to_s.upcase if format.is_a?(Symbol) - info " Processing by #{payload[:controller]}##{payload[:action]} as #{payload[:formats].first.to_s.upcase}" + info " Processing by #{payload[:controller]}##{payload[:action]} as #{format}" info " Parameters: #{params.inspect}" unless params.empty? end diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb index 006b9fd456..05dca445a4 100644 --- a/actionpack/lib/action_controller/metal/compatibility.rb +++ b/actionpack/lib/action_controller/metal/compatibility.rb @@ -18,13 +18,10 @@ module ActionController delegate :default_charset=, :to => "ActionDispatch::Response" end - # TODO: Update protected instance variables list - config_accessor :protected_instance_variables - self.protected_instance_variables = %w(@assigns @performed_redirect @performed_render - @variables_added @request_origin @url - @parent_controller @action_name - @before_filter_chain_aborted @_headers @_params - @_response) + self.protected_instance_variables = %w( + @_status @_headers @_params @_env @_response @_request + @_view_runtime @_stream @_url_options @_action_has_layout + ) def rescue_action(env) raise env["action_dispatch.rescue.exception"] diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb index 3ec0c4c6a4..e8e465d3ba 100644 --- a/actionpack/lib/action_controller/metal/implicit_render.rb +++ b/actionpack/lib/action_controller/metal/implicit_render.rb @@ -1,21 +1,19 @@ module ActionController module ImplicitRender def send_action(method, *args) - if respond_to?(method, true) - ret = super - default_render unless response_body - ret - else - default_render - end + ret = super + default_render unless response_body + ret end def default_render(*args) render(*args) end - def action_method?(action_name) - super || template_exists?(action_name.to_s, _prefixes) + def method_for_action(action_name) + super || if template_exists?(action_name.to_s, _prefixes) + "default_render" + end end end end diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index 4e54c2ad88..16cbbce2fb 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -19,7 +19,7 @@ module ActionController :controller => self.class.name, :action => self.action_name, :params => request.filtered_parameters, - :formats => request.formats.map(&:to_sym), + :format => request.format.ref, :method => request.method, :path => (request.fullpath rescue "unknown") } diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index 21bbe17dc3..9b27bb8b91 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -125,7 +125,7 @@ module ActionController # module is inherited. def inherited(klass) if klass._wrapper_options[:format].present? - klass._set_wrapper_defaults(klass._wrapper_options) + klass._set_wrapper_defaults(klass._wrapper_options.slice(:format)) end super end @@ -136,15 +136,23 @@ module ActionController # this could be done by trying to find the defined model that has the # same singularize name as the controller. For example, +UsersController+ # will try to find if the +User+ model exists. - def _default_wrap_model + # + # This method also does namespace lookup. Foo::Bar::UsersController will + # try to find Foo::Bar::User, Foo::User and finally User. + def _default_wrap_model #:nodoc: model_name = self.name.sub(/Controller$/, '').singularize begin model_klass = model_name.constantize - rescue NameError => e - unscoped_model_name = model_name.split("::", 2).last - break if unscoped_model_name == model_name - model_name = unscoped_model_name + rescue NameError, ArgumentError => e + if e.message =~ /is not missing constant|uninitialized constant #{model_name}/ + namespaces = model_name.split("::") + namespaces.delete_at(-2) + break if namespaces.last == model_name + model_name = namespaces.join("::") + else + raise + end end until model_klass model_klass diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 1cd93a188c..13044a7450 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -73,7 +73,10 @@ module ActionController #:nodoc: protected # The actual before_filter that is used. Modify this to change how you handle unverified requests. def verify_authenticity_token - verified_request? || handle_unverified_request + unless verified_request? + logger.debug "WARNING: Can't verify CSRF token authenticity" if logger + handle_unverified_request + end end def handle_unverified_request diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index 59a3621f72..ebadb29ea7 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -68,7 +68,7 @@ module ActionController #:nodoc: # respond_with(@project, @task) # end # - # Giving an array of resources, you ensure that the responder will redirect to + # Giving several resources ensures that the responder will redirect to # <code>project_task_url</code> instead of <code>task_url</code>. # # Namespaced and singleton resources require a symbol to be given, as in @@ -77,6 +77,11 @@ module ActionController #:nodoc: # # respond_with(@project, :manager, @task) # + # Note that if you give an array, it will be treated as a collection, + # so the following is not equivalent: + # + # respond_with [@project, :manager, @task] + # # === Custom options # # <code>respond_with</code> also allow you to pass options that are forwarded diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb index 09dd08898c..91a97c02ff 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb @@ -33,7 +33,7 @@ module HTML result = super # strip any comments, and if they have a newline at the end (ie. line with # only a comment) strip that too - result.gsub!(/<!--(.*?)-->[\n]?/m, "") if result + result = result.gsub(/<!--(.*?)-->[\n]?/m, "") if (result && result =~ /<!--(.*?)-->[\n]?/m) # Recurse - handle all dirty nested tags result == text ? result : sanitize(result, options) end diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index 4f4cb96a74..aaed0d750f 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -42,20 +42,6 @@ module ActionDispatch attr_reader :cache_control, :etag alias :etag? :etag - def initialize(*) - super - - @cache_control = {} - @etag = self["ETag"] - - if cache_control = self["Cache-Control"] - cache_control.split(/,\s*/).each do |segment| - first, last = segment.split("=") - @cache_control[first.to_sym] = last || true - end - end - end - def last_modified if last = headers['Last-Modified'] Time.httpdate(last) @@ -77,6 +63,18 @@ module ActionDispatch private + def prepare_cache_control! + @cache_control = {} + @etag = self["ETag"] + + if cache_control = self["Cache-Control"] + cache_control.split(/,\s*/).each do |segment| + first, last = segment.split("=") + @cache_control[first.to_sym] = last || true + end + end + end + def handle_conditional_get! if etag? || last_modified? || !@cache_control.empty? set_conditional_cache_control! diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index f07ac44f7a..ccb866f4f7 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -17,16 +17,17 @@ module ActionDispatch include ActionDispatch::Http::Upload include ActionDispatch::Http::URL - LOCALHOST = [/^127\.0\.0\.\d{1,3}$/, "::1", /^0:0:0:0:0:0:0:1(%.*)?$/].freeze - - %w[ AUTH_TYPE GATEWAY_INTERFACE + LOCALHOST = [/^127\.0\.0\.\d{1,3}$/, "::1", /^0:0:0:0:0:0:0:1(%.*)?$/].freeze + ENV_METHODS = %w[ AUTH_TYPE GATEWAY_INTERFACE PATH_TRANSLATED REMOTE_HOST REMOTE_IDENT REMOTE_USER REMOTE_ADDR SERVER_NAME SERVER_PROTOCOL HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM - HTTP_NEGOTIATE HTTP_PRAGMA ].each do |env| + HTTP_NEGOTIATE HTTP_PRAGMA ].freeze + + ENV_METHODS.each do |env| class_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{env.sub(/^HTTP_/n, '').downcase} @env["#{env}"] diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 1f4f3ac0da..3a6b1da4fd 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -56,26 +56,25 @@ module ActionDispatch # :nodoc: cattr_accessor(:default_charset) { "utf-8" } - module Setup - def initialize(status = 200, header = {}, body = []) - self.body, self.header, self.status = body, header, status + include Rack::Response::Helpers + include ActionDispatch::Http::Cache::Response - @sending_file = false - @blank = false + def initialize(status = 200, header = {}, body = []) + self.body, self.header, self.status = body, header, status - if content_type = self["Content-Type"] - type, charset = content_type.split(/;\s*charset=/) - @content_type = Mime::Type.lookup(type) - @charset = charset || "UTF-8" - end + @sending_file = false + @blank = false - yield self if block_given? + if content_type = self["Content-Type"] + type, charset = content_type.split(/;\s*charset=/) + @content_type = Mime::Type.lookup(type) + @charset = charset || "UTF-8" end - end - include Rack::Response::Helpers - include Setup - include ActionDispatch::Http::Cache::Response + prepare_cache_control! + + yield self if block_given? + end def status=(status) @status = Rack::Utils.status_code(status) @@ -116,9 +115,32 @@ module ActionDispatch # :nodoc: EMPTY = " " + class BodyBuster #:nodoc: + def initialize(response) + @response = response + @body = "" + end + + def bust(body) + body.call(@response, self) + body.close if body.respond_to?(:close) + @body + end + + def write(string) + @body << string.to_s + end + end + def body=(body) @blank = true if body == EMPTY + if body.respond_to?(:call) + ActiveSupport::Deprecation.warn "Setting a Proc or an object that responds to call " \ + "in response_body is no longer supported", caller + body = BodyBuster.new(self).bust(body) + end + # 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). @@ -150,6 +172,10 @@ module ActionDispatch # :nodoc: headers['Location'] = url end + def close + @body.close if @body.respond_to?(:close) + end + def to_a assign_default_content_type_and_charset! handle_conditional_get! diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb index 97f7cf0bbe..0c5bafa666 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb @@ -24,7 +24,7 @@ <div id="session_dump" style="display:none"><pre><%= debug_hash @request.session %></pre></div> <p><a href="#" onclick="document.getElementById('env_dump').style.display='block'; return false;">Show env dump</a></p> -<div id="env_dump" style="display:none"><pre><%= debug_hash @request.env %></pre></div> +<div id="env_dump" style="display:none"><pre><%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %></pre></div> <h2 style="margin-top: 30px">Response</h2> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb index 2099fd069a..4b9d3141d5 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb @@ -1,7 +1,7 @@ <h1> <%=h @exception.class.to_s %> <% if @request.parameters['controller'] %> - in <%=h @request.parameters['controller'].classify.pluralize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %> + in <%=h @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %> <% end %> </h1> <pre><%=h @exception.message %></pre> diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 97e8ccc9a5..5097f6732d 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -224,6 +224,7 @@ module ActionDispatch self.valid_conditions.push(:controller, :action) @append = [] + @prepend = [] @disable_clear_and_finalize = false clear! end @@ -232,7 +233,6 @@ module ActionDispatch clear! unless @disable_clear_and_finalize eval_block(block) finalize! unless @disable_clear_and_finalize - nil end @@ -240,6 +240,10 @@ module ActionDispatch @append << block end + def prepend(&block) + @prepend << block + end + def eval_block(block) if block.arity == 1 raise "You are using the old router DSL which has been removed in Rails 3.1. " << @@ -262,8 +266,6 @@ module ActionDispatch end def clear! - # Clear the controller cache so we may discover new ones - @controller_constraints = nil @finalized = false routes.clear named_routes.clear @@ -271,6 +273,7 @@ module ActionDispatch :parameters_key => PARAMETERS_KEY, :request_class => request_class ) + @prepend.each { |blk| eval_block(blk) } end def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false) diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb index d430691429..397bda41d5 100644 --- a/actionpack/lib/action_dispatch/testing/test_process.rb +++ b/actionpack/lib/action_dispatch/testing/test_process.rb @@ -29,7 +29,7 @@ module ActionDispatch @response.redirect_url end - # Shortcut for <tt>ARack::Test::UploadedFile.new(ActionController::TestCase.fixture_path + path, type)</tt>: + # Shortcut for <tt>Rack::Test::UploadedFile.new(ActionController::TestCase.fixture_path + path, type)</tt>: # # post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png') # diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb index 170ceb299a..584e5c3791 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -3,7 +3,7 @@ module ActionPack MAJOR = 3 MINOR = 1 TINY = 0 - PRE = "beta" + PRE = "beta1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 92b6f7c770..a67b61c1ef 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -50,6 +50,7 @@ module ActionView autoload :Resolver autoload :PathResolver autoload :FileSystemResolver + autoload :OptimizedFileSystemResolver autoload :FallbackFileSystemResolver end diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index ead7feb091..3b5f4e694f 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -135,8 +135,12 @@ module ActionView # for elements that will be fragment cached. def content_for(name, content = nil, &block) content = capture(&block) if block_given? - result = @view_flow.append(name, content) if content - result unless content + if content + @view_flow.append(name, content) + nil + else + @view_flow.get(name) + end end # The same as +content_for+ but when used with streaming flushes diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 326ad40c8d..853ab061d2 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -94,9 +94,20 @@ module ActionView when 43200..86399 then locale.t :about_x_months, :count => 1 when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round else - distance_in_years = distance_in_minutes / 525600 - minute_offset_for_leap_year = (distance_in_years / 4) * 1440 - remainder = ((distance_in_minutes - minute_offset_for_leap_year) % 525600) + fyear = from_time.year + fyear += 1 if from_time.month >= 3 + tyear = to_time.year + tyear -= 1 if to_time.month < 3 + leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| Date.leap?(x)} + minute_offset_for_leap_year = leap_years * 1440 + # Discount the leap year days when calculating year distance. + # e.g. if there are 20 leap year days between 2 dates having the same day + # and month then the based on 365 days calculation + # the distance in years will come out to over 80 years when in written + # english it would read better as about 80 years. + minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year + remainder = (minutes_with_offset % 525600) + distance_in_years = (minutes_with_offset / 525600) if remainder < 131400 locale.t(:about_x_years, :count => distance_in_years) elsif remainder < 394200 @@ -207,7 +218,8 @@ module ActionView # Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a # specified time-based attribute (identified by +method+) on an object assigned to the template (identified by - # +object+). You can include the seconds with <tt>:include_seconds</tt>. + # +object+). You can include the seconds with <tt>:include_seconds</tt>. You can get hours in the AM/PM format + # with <tt>:ampm</tt> option. # # This method will also generate 3 input hidden tags, for the actual year, month and day unless the option # <tt>:ignore_date</tt> is set to +true+. If you set the <tt>:ignore_date</tt> to +true+, you must have a @@ -231,6 +243,9 @@ module ActionView # time_select("post", "written_on", :prompt => {:hour => true}) # generic prompt for hours # time_select("post", "written_on", :prompt => true) # generic prompts for all # + # # You can set :ampm option to true which will show the hours as: 12 PM, 01 AM .. 11 PM. + # time_select 'game', 'game_time', {:ampm => true} + # # The selects are prepared for multi-parameter assignment to an Active Record object. # # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that @@ -258,6 +273,9 @@ module ActionView # # be stored in the trip variable in the departing attribute. # datetime_select("trip", "departing", :default => 3.days.from_now) # + # # Generate a datetime select with hours in the AM/PM format + # datetime_select("post", "written_on", :ampm => true) + # # # Generates a datetime select that discards the type that, when POSTed, will be stored in the post variable # # as the written_on attribute. # datetime_select("post", "written_on", :discard_type => true) @@ -307,6 +325,9 @@ module ActionView # # my_date_time (four days after today) # select_datetime(my_date_time, :discard_type => true) # + # # Generate a datetime field with hours in the AM/PM format + # select_datetime(my_date_time, :ampm => true) + # # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today) # # prefixed with 'payday' rather than 'date' # select_datetime(my_date_time, :prefix => 'payday') @@ -388,7 +409,10 @@ module ActionView # # separated by ':' and includes an input for seconds. # select_time(my_time, :time_separator => ':', :include_seconds => true) # - # # Generates a time select with a custom prompt. Use <tt>:prompt => true</tt> for generic prompts. + # # Generate a time select field with hours in the AM/PM format + # select_time(my_time, :ampm => true) + # + # # Generates a time select with a custom prompt. Use <tt>:prompt</tt> to true for generic prompts. # select_time(my_time, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'}) # select_time(my_time, :prompt => {:hour => true}) # generic prompt for hours # select_time(my_time, :prompt => true) # generic prompts for all @@ -469,6 +493,9 @@ module ActionView # # generic prompt. # select_hour(13, :prompt => 'Choose hour') # + # # Generate a select field for hours in the AM/PM format + # select_hour(my_time, :ampm => true) + # def select_hour(datetime, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_hour end @@ -602,6 +629,15 @@ module ActionView :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }.freeze + AMPM_TRANSLATION = Hash[ + [[0, "12 AM"], [1, "01 AM"], [2, "02 AM"], [3, "03 AM"], + [4, "04 AM"], [5, "05 AM"], [6, "06 AM"], [7, "07 AM"], + [8, "08 AM"], [9, "09 AM"], [10, "10 AM"], [11, "11 AM"], + [12, "12 PM"], [13, "01 PM"], [14, "02 PM"], [15, "03 PM"], + [16, "04 PM"], [17, "05 PM"], [18, "06 PM"], [19, "07 PM"], + [20, "08 PM"], [21, "09 PM"], [22, "10 PM"], [23, "11 PM"]] + ].freeze + def initialize(datetime, options = {}, html_options = {}) @options = options.dup @html_options = html_options.dup @@ -693,7 +729,7 @@ module ActionView if @options[:use_hidden] || @options[:discard_hour] build_hidden(:hour, hour) else - build_options_and_select(:hour, hour, :end => 23) + build_options_and_select(:hour, hour, :end => 23, :ampm => @options[:ampm]) end end @@ -817,7 +853,7 @@ module ActionView start = options.delete(:start) || 0 stop = options.delete(:end) || 59 step = options.delete(:step) || 1 - options.reverse_merge!({:leading_zeros => true}) + options.reverse_merge!({:leading_zeros => true, :ampm => false}) leading_zeros = options.delete(:leading_zeros) select_options = [] @@ -825,7 +861,8 @@ module ActionView value = leading_zeros ? sprintf("%02d", i) : i tag_options = { :value => value } tag_options[:selected] = "selected" if selected == i - select_options << content_tag(:option, value, tag_options) + text = options[:ampm] ? AMPM_TRANSLATION[i] : value + select_options << content_tag(:option, text, tag_options) end (select_options.join("\n") + "\n").html_safe end diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index 06975ffa2f..f0ed3425de 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -167,12 +167,12 @@ module ActionView @frozen_formats = true end - # Overload formats= to reject ["*/*"] values. + # Overload formats= to expand ["*/*"] values and automatically + # add :html as fallback to :js. def formats=(values) - if values && values.size == 1 - value = values.first - values = nil if value == "*/*" - values << :html if value == :js + if values + values.concat(_formats_defaults) if values.delete "*/*" + values << :html if values == [:js] end super(values) end diff --git a/actionpack/lib/action_view/path_set.rb b/actionpack/lib/action_view/path_set.rb index 8b840a6463..e0cb5d6a70 100644 --- a/actionpack/lib/action_view/path_set.rb +++ b/actionpack/lib/action_view/path_set.rb @@ -35,7 +35,7 @@ module ActionView #:nodoc: each_with_index do |path, i| path = path.to_s if path.is_a?(Pathname) next unless path.is_a?(String) - self[i] = FileSystemResolver.new(path) + self[i] = OptimizedFileSystemResolver.new(path) end end end diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb index e246646963..d4448a7b33 100644 --- a/actionpack/lib/action_view/template/error.rb +++ b/actionpack/lib/action_view/template/error.rb @@ -1,3 +1,4 @@ +require "active_support/core_ext/array/wrap" require "active_support/core_ext/enumerable" module ActionView @@ -29,6 +30,7 @@ module ActionView def initialize(paths, path, prefixes, partial, details, *) @path = path + prefixes = Array.wrap(prefixes) display_paths = paths.compact.map{ |p| p.to_s.inspect }.join(", ") template_type = if partial "partial" diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index 870897958a..2b9427ace5 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -10,17 +10,16 @@ module ActionView attr_reader :name, :prefix, :partial, :virtual alias_method :partial?, :partial - def initialize(name, prefix, partial) - @name, @prefix, @partial = name, prefix, partial - rebuild(@name, @prefix, @partial) + def self.build(name, prefix, partial) + virtual = "" + virtual << "#{prefix}/" unless prefix.empty? + virtual << (partial ? "_#{name}" : name) + new name, prefix, partial, virtual end - def rebuild(name, prefix, partial) - @virtual = "" - @virtual << "#{prefix}/" unless prefix.empty? - @virtual << (partial ? "_#{name}" : name) - - self.replace(@virtual) + def initialize(name, prefix, partial, virtual) + @name, @prefix, @partial = name, prefix, partial + super(virtual) end end @@ -60,7 +59,7 @@ module ActionView # Helpers that builds a path. Useful for building virtual paths. def build_path(name, prefix, partial) - Path.new(name, prefix, partial) + Path.build(name, prefix, partial) end # Handles templates caching. If a key is given and caching is on @@ -112,7 +111,8 @@ module ActionView end end - class PathResolver < Resolver + # An abstract class that implements a Resolver with path semantics. + class PathResolver < Resolver #:nodoc: EXTENSIONS = [:locale, :formats, :handlers] DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}" @@ -124,13 +124,12 @@ module ActionView private def find_templates(name, prefix, partial, details) - path = build_path(name, prefix, partial) - extensions = Hash[EXTENSIONS.map { |ext| [ext, details[ext]] }.flatten(0)] - query(path, extensions, details[:formats]) + path = Path.build(name, prefix, partial) + query(path, details, details[:formats]) end - def query(path, exts, formats) - query = build_query(path, exts) + def query(path, details, formats) + query = build_query(path, details) templates = [] sanitizer = Hash.new { |h,k| h[k] = Dir["#{File.dirname(k)}/*"] } @@ -138,7 +137,7 @@ module ActionView next if File.directory?(p) || !sanitizer[p].include?(p) handler, format = extract_handler_and_format(p, formats) - contents = File.open(p, "rb") {|io| io.read } + contents = File.open(p, "rb") { |io| io.read } templates << Template.new(contents, File.expand_path(p), handler, :virtual_path => path.virtual, :format => format, :updated_at => mtime(p)) @@ -147,18 +146,15 @@ module ActionView templates end - # Helper for building query glob string based on resolver's pattern. - def build_query(path, exts) + # Helper for building query glob string based on resolver's pattern. + def build_query(path, details) query = @pattern.dup query.gsub!(/\:prefix(\/)?/, path.prefix.empty? ? "" : "#{path.prefix}\\1") # prefix can be empty... query.gsub!(/\:action/, path.partial? ? "_#{path.name}" : path.name) - exts.each { |ext, variants| + details.each do |ext, variants| query.gsub!(/\:#{ext}/, "{#{variants.compact.uniq.join(',')}}") - } - - query.gsub!('.{html,', '.{html,text.html,') - query.gsub!('.{text,', '.{text,text.plain,') + end File.expand_path(query, @path) end @@ -235,9 +231,25 @@ module ActionView alias :== :eql? end + # An Optimized resolver for Rails' most common case. + class OptimizedFileSystemResolver < FileSystemResolver #:nodoc: + def build_query(path, details) + exts = EXTENSIONS.map { |ext| details[ext] } + query = File.join(@path, path) + + exts.each do |ext| + query << "{" + ext.compact.each { |e| query << ".#{e}," } + query << "}" + end + + query + end + end + # The same as FileSystemResolver but does not allow templates to store # a virtual path since it is invalid for such resolvers. - class FallbackFileSystemResolver < FileSystemResolver + class FallbackFileSystemResolver < FileSystemResolver #:nodoc: def self.instances [new(""), new("/")] end diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb index 9c10decd60..c75b7d4de0 100644 --- a/actionpack/lib/sprockets/railtie.rb +++ b/actionpack/lib/sprockets/railtie.rb @@ -34,12 +34,20 @@ module Sprockets app.assets = asset_environment(app) ActiveSupport.on_load(:action_view) do - app.assets.context.instance_eval do + if app.assets.respond_to?(:context_class) + context = app.assets.context_class + + # TODO: Remove this condition when Sprockets 2.0.beta.3 is released + else + context = app.assets.context + end + + context.instance_eval do include ::ActionView::Helpers::SprocketsHelper end end - app.routes.append do + app.routes.prepend do mount app.assets => assets.prefix end diff --git a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb index 99f09286ff..97be5a5bb0 100644 --- a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb +++ b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb @@ -36,7 +36,7 @@ class RenderPartialWithRecordIdentificationController < ActionController::Base end def render_with_record_collection - @developers = Developer.find(:all) + @developers = Developer.all render :partial => @developers end diff --git a/actionpack/test/controller/new_base/render_implicit_action_test.rb b/actionpack/test/controller/new_base/render_implicit_action_test.rb index 3bb3016fdb..1e2191d417 100644 --- a/actionpack/test/controller/new_base/render_implicit_action_test.rb +++ b/actionpack/test/controller/new_base/render_implicit_action_test.rb @@ -33,10 +33,10 @@ module RenderImplicitAction assert_status 200 end - test "action_method? returns true for implicit actions" do - assert SimpleController.new.action_method?(:hello_world) - assert SimpleController.new.action_method?(:"hyphen-ated") - assert SimpleController.new.action_method?(:not_implemented) + test "available_action? returns true for implicit actions" do + assert SimpleController.new.available_action?(:hello_world) + assert SimpleController.new.available_action?(:"hyphen-ated") + assert SimpleController.new.available_action?(:not_implemented) end end end diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb index 548cd02dc0..85464fc780 100644 --- a/actionpack/test/controller/params_wrapper_test.rb +++ b/actionpack/test/controller/params_wrapper_test.rb @@ -170,28 +170,36 @@ end class NamespacedParamsWrapperTest < ActionController::TestCase module Admin - class UsersController < ActionController::Base - class << self - attr_accessor :last_parameters - end - - def parse - self.class.last_parameters = request.params.except(:controller, :action) - head :ok + module Users + class UsersController < ActionController::Base; + class << self + attr_accessor :last_parameters + end + + def parse + self.class.last_parameters = request.params.except(:controller, :action) + head :ok + end end end end - class Sample + class SampleOne def self.column_names ["username"] end end - tests Admin::UsersController + class SampleTwo + def self.column_names + ["title"] + end + end + + tests Admin::Users::UsersController def teardown - Admin::UsersController.last_parameters = nil + Admin::Users::UsersController.last_parameters = nil end def test_derived_name_from_controller @@ -203,7 +211,7 @@ class NamespacedParamsWrapperTest < ActionController::TestCase end def test_namespace_lookup_from_model - Admin.const_set(:User, Class.new(Sample)) + Admin.const_set(:User, Class.new(SampleOne)) begin with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' @@ -216,20 +224,15 @@ class NamespacedParamsWrapperTest < ActionController::TestCase end def test_hierarchy_namespace_lookup_from_model - # Make sure that we cleanup ::Admin::User - admin_user_constant = ::Admin::User - ::Admin.send :remove_const, :User - - Object.const_set(:User, Class.new(Sample)) + Object.const_set(:User, Class.new(SampleTwo)) begin with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } - assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) + assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'title' => 'Developer' }}) end ensure Object.send :remove_const, :User - ::Admin.const_set(:User, admin_user_constant) end end @@ -241,6 +244,6 @@ class NamespacedParamsWrapperTest < ActionController::TestCase end def assert_parameters(expected) - assert_equal expected, Admin::UsersController.last_parameters + assert_equal expected, Admin::Users::UsersController.last_parameters end -end +end
\ No newline at end of file diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index 31f4bf3a76..dea80ed887 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -81,22 +81,25 @@ module RequestForgeryProtectionTests @token = "cf50faa3fe97702ca1ae" ActiveSupport::SecureRandom.stubs(:base64).returns(@token) - ActionController::Base.request_forgery_protection_token = :authenticity_token + ActionController::Base.request_forgery_protection_token = :custom_authenticity_token end + def teardown + ActionController::Base.request_forgery_protection_token = nil + end def test_should_render_form_with_token_tag assert_not_blocked do get :index end - assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token + assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token end def test_should_render_button_to_with_token_tag assert_not_blocked do get :show_button end - assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token + assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token end def test_should_allow_get @@ -128,15 +131,15 @@ module RequestForgeryProtectionTests end def test_should_allow_post_with_token - assert_not_blocked { post :index, :authenticity_token => @token } + assert_not_blocked { post :index, :custom_authenticity_token => @token } end def test_should_allow_put_with_token - assert_not_blocked { put :index, :authenticity_token => @token } + assert_not_blocked { put :index, :custom_authenticity_token => @token } end def test_should_allow_delete_with_token - assert_not_blocked { delete :index, :authenticity_token => @token } + assert_not_blocked { delete :index, :custom_authenticity_token => @token } end def test_should_allow_post_with_token_in_header @@ -172,10 +175,18 @@ end class RequestForgeryProtectionControllerTest < ActionController::TestCase include RequestForgeryProtectionTests + setup do + ActionController::Base.request_forgery_protection_token = :custom_authenticity_token + end + + teardown do + ActionController::Base.request_forgery_protection_token = nil + end + test 'should emit a csrf-param meta tag and a csrf-token meta tag' do ActiveSupport::SecureRandom.stubs(:base64).returns(@token + '<=?') get :meta - assert_select 'meta[name=?][content=?]', 'csrf-param', 'authenticity_token' + assert_select 'meta[name=?][content=?]', 'csrf-param', 'custom_authenticity_token' assert_select 'meta[name=?][content=?]', 'csrf-token', 'cf50faa3fe97702ca1ae<=?' end end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index d128006404..25b1b4f745 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -358,6 +358,13 @@ class RequestTest < ActiveSupport::TestCase assert request.head? end + test "post masquerading as put" do + request = stub_request 'REQUEST_METHOD' => 'PUT', "rack.methodoverride.original_method" => "POST" + assert_equal "POST", request.method + assert_equal "PUT", request.request_method + assert request.put? + end + test "xml format" do request = stub_request request.expects(:parameters).at_least_once.returns({ :format => 'xml' }) diff --git a/actionpack/test/dispatch/response_body_is_proc_test.rb b/actionpack/test/dispatch/response_body_is_proc_test.rb new file mode 100644 index 0000000000..fd94832624 --- /dev/null +++ b/actionpack/test/dispatch/response_body_is_proc_test.rb @@ -0,0 +1,37 @@ +require 'abstract_unit' + +class ResponseBodyIsProcTest < ActionDispatch::IntegrationTest + class TestController < ActionController::Base + def test + self.response_body = proc { |response, output| + output.write 'Hello' + } + end + end + + def test_simple_get + with_test_route_set do + assert_deprecated do + get '/test' + end + assert_response :success + assert_equal 'Hello', response.body + end + end + + private + + def with_test_route_set(options = {}) + with_routing do |set| + set.draw do + match ':action', :to => ::ResponseBodyIsProcTest::TestController + end + + @app = self.class.build_app(set) do |middleware| + middleware.delete "ActionDispatch::ShowExceptions" + end + + yield + end + end +end diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb index cc57a6cba0..42f6c7f79f 100644 --- a/actionpack/test/dispatch/show_exceptions_test.rb +++ b/actionpack/test/dispatch/show_exceptions_test.rb @@ -131,11 +131,11 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest 'action_dispatch.request.parameters' => { 'action' => 'show', 'id' => 'unknown', - 'controller' => 'featured_tiles' + 'controller' => 'featured_tile' } }) assert_response 500 - assert_match(/RuntimeError\n in FeaturedTilesController/, body) + assert_match(/RuntimeError\n in FeaturedTileController/, body) end test "sets the HTTP charset parameter" do diff --git a/actionpack/test/template/capture_helper_test.rb b/actionpack/test/template/capture_helper_test.rb index 592c7da060..a9157e711c 100644 --- a/actionpack/test/template/capture_helper_test.rb +++ b/actionpack/test/template/capture_helper_test.rb @@ -38,7 +38,15 @@ class CaptureHelperTest < ActionView::TestCase assert_equal '<em>bar</em>', string end - def test_content_for + def test_capture_used_for_read + content_for :foo, "foo" + assert_equal "foo", content_for(:foo) + + content_for(:bar){ "bar" } + assert_equal "bar", content_for(:bar) + end + + def test_content_for_question_mark assert ! content_for?(:title) content_for :title, 'title' assert content_for?(:title) @@ -49,14 +57,14 @@ class CaptureHelperTest < ActionView::TestCase assert !content_for?(:title) provide :title, "hi" assert content_for?(:title) - assert_equal "hi", @view_flow.get(:title) + assert_equal "hi", content_for(:title) provide :title, "<p>title</p>" - assert_equal "hi<p>title</p>", @view_flow.get(:title) + assert_equal "hi<p>title</p>", content_for(:title) @view_flow = ActionView::OutputFlow.new provide :title, "hi" provide :title, "<p>title</p>".html_safe - assert_equal "hi<p>title</p>", @view_flow.get(:title) + assert_equal "hi<p>title</p>", content_for(:title) end def test_with_output_buffer_swaps_the_output_buffer_given_no_argument diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index 12d2410f49..09c53a36f0 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -121,6 +121,10 @@ class DateHelperTest < ActionView::TestCase start_date = Date.new 1975, 1, 31 end_date = Date.new 1977, 1, 31 assert_equal("about 2 years", distance_of_time_in_words(start_date, end_date)) + + start_date = Date.new 1982, 12, 3 + end_date = Date.new 2010, 11, 30 + assert_equal("almost 28 years", distance_of_time_in_words(start_date, end_date)) end def test_distance_in_words_with_integers @@ -416,6 +420,14 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18)) end + def test_select_hour_with_ampm + expected = %(<select id="date_hour" name="date[hour]">\n) + expected << %(<option value="00">12 AM</option>\n<option value="01">01 AM</option>\n<option value="02">02 AM</option>\n<option value="03">03 AM</option>\n<option value="04">04 AM</option>\n<option value="05">05 AM</option>\n<option value="06">06 AM</option>\n<option value="07">07 AM</option>\n<option value="08" selected="selected">08 AM</option>\n<option value="09">09 AM</option>\n<option value="10">10 AM</option>\n<option value="11">11 AM</option>\n<option value="12">12 PM</option>\n<option value="13">01 PM</option>\n<option value="14">02 PM</option>\n<option value="15">03 PM</option>\n<option value="16">04 PM</option>\n<option value="17">05 PM</option>\n<option value="18">06 PM</option>\n<option value="19">07 PM</option>\n<option value="20">08 PM</option>\n<option value="21">09 PM</option>\n<option value="22">10 PM</option>\n<option value="23">11 PM</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :ampm => true) + end + def test_select_hour_with_disabled expected = %(<select id="date_hour" name="date[hour]" disabled="disabled">\n) expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n) @@ -937,6 +949,35 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :start_year => 2003, :end_year => 2005, :prefix => "date[first]") end + def test_select_datetime_with_ampm + expected = %(<select id="date_first_year" name="date[first][year]">\n) + expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) + expected << "</select>\n" + + expected << %(<select id="date_first_month" name="date[first][month]">\n) + expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) + expected << "</select>\n" + + expected << %(<select id="date_first_day" name="date[first][day]">\n) + expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) + expected << "</select>\n" + + expected << " — " + + expected << %(<select id="date_first_hour" name="date[first][hour]">\n) + expected << %(<option value="00">12 AM</option>\n<option value="01">01 AM</option>\n<option value="02">02 AM</option>\n<option value="03">03 AM</option>\n<option value="04">04 AM</option>\n<option value="05">05 AM</option>\n<option value="06">06 AM</option>\n<option value="07">07 AM</option>\n<option value="08" selected="selected">08 AM</option>\n<option value="09">09 AM</option>\n<option value="10">10 AM</option>\n<option value="11">11 AM</option>\n<option value="12">12 PM</option>\n<option value="13">01 PM</option>\n<option value="14">02 PM</option>\n<option value="15">03 PM</option>\n<option value="16">04 PM</option>\n<option value="17">05 PM</option>\n<option value="18">06 PM</option>\n<option value="19">07 PM</option>\n<option value="20">08 PM</option>\n<option value="21">09 PM</option>\n<option value="22">10 PM</option>\n<option value="23">11 PM</option>\n) + expected << "</select>\n" + + expected << " : " + + expected << %(<select id="date_first_minute" name="date[first][minute]">\n) + expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :start_year => 2003, :end_year => 2005, :prefix => "date[first]", :ampm => true) + end + + def test_select_datetime_with_separators expected = %(<select id="date_first_year" name="date[first][year]">\n) expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) @@ -1136,6 +1177,24 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => false) end + def test_select_time_with_ampm + expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n) + expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" />\n) + expected << %(<input name="date[day]" id="date_day" value="16" type="hidden" />\n) + + expected << %(<select id="date_hour" name="date[hour]">\n) + expected << %(<option value="00">12 AM</option>\n<option value="01">01 AM</option>\n<option value="02">02 AM</option>\n<option value="03">03 AM</option>\n<option value="04">04 AM</option>\n<option value="05">05 AM</option>\n<option value="06">06 AM</option>\n<option value="07">07 AM</option>\n<option value="08" selected="selected">08 AM</option>\n<option value="09">09 AM</option>\n<option value="10">10 AM</option>\n<option value="11">11 AM</option>\n<option value="12">12 PM</option>\n<option value="13">01 PM</option>\n<option value="14">02 PM</option>\n<option value="15">03 PM</option>\n<option value="16">04 PM</option>\n<option value="17">05 PM</option>\n<option value="18">06 PM</option>\n<option value="19">07 PM</option>\n<option value="20">08 PM</option>\n<option value="21">09 PM</option>\n<option value="22">10 PM</option>\n<option value="23">11 PM</option>\n) + expected << "</select>\n" + + expected << " : " + + expected << %(<select id="date_minute" name="date[minute]">\n) + expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => false, :ampm => true) + end + def test_select_time_with_separator expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n) expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" />\n) diff --git a/actionpack/test/template/html-scanner/sanitizer_test.rb b/actionpack/test/template/html-scanner/sanitizer_test.rb index fcc3782f04..678cb9eeeb 100644 --- a/actionpack/test/template/html-scanner/sanitizer_test.rb +++ b/actionpack/test/template/html-scanner/sanitizer_test.rb @@ -20,6 +20,7 @@ class SanitizerTest < ActionController::TestCase assert_equal "This has a here.", sanitizer.sanitize("This has a <![CDATA[<section>]]> here.") assert_equal "This has an unclosed ", sanitizer.sanitize("This has an unclosed <![CDATA[<section>]] here...") [nil, '', ' '].each { |blank| assert_equal blank, sanitizer.sanitize(blank) } + assert_nothing_raised { sanitizer.sanitize("This is a frozen string with no tags".freeze) } end def test_strip_links diff --git a/actionpack/test/template/lookup_context_test.rb b/actionpack/test/template/lookup_context_test.rb index 5fb1fdc044..47b70f05ab 100644 --- a/actionpack/test/template/lookup_context_test.rb +++ b/actionpack/test/template/lookup_context_test.rb @@ -51,6 +51,11 @@ class LookupContextTest < ActiveSupport::TestCase assert_equal Mime::SET, @lookup_context.formats end + test "handles explicitly defined */* formats fallback to :js" do + @lookup_context.formats = [:js, Mime::ALL] + assert_equal [:js, *Mime::SET.symbols], @lookup_context.formats + end + test "adds :html fallback to :js formats" do @lookup_context.formats = [:js] assert_equal [:js, :html], @lookup_context.formats @@ -251,4 +256,13 @@ class TestMissingTemplate < ActiveSupport::TestCase end assert_match %r{Missing partial parent/foo, child/foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message end + + test "if a single prefix is passed as a string and the lookup fails, MissingTemplate accepts it" do + e = assert_raise ActionView::MissingTemplate do + details = {:handlers=>[], :formats=>[], :locale=>[]} + @lookup_context.view_paths.find("foo", "parent", true, details) + end + assert_match %r{Missing partial parent/foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message + end + end diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index d4e912c410..86d08a43a5 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -325,7 +325,7 @@ class CachedViewRenderTest < ActiveSupport::TestCase # Ensure view path cache is primed def setup view_paths = ActionController::Base.view_paths - assert_equal ActionView::FileSystemResolver, view_paths.first.class + assert_equal ActionView::OptimizedFileSystemResolver, view_paths.first.class setup_view(view_paths) end diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG index 03287fac7a..be4de2e53c 100644 --- a/activemodel/CHANGELOG +++ b/activemodel/CHANGELOG @@ -1,5 +1,7 @@ *Rails 3.1.0 (unreleased)* +* attr_accessible and friends now accepts :as as option to specify a role [Josh Kalderimis] + * Add support for proc or lambda as an option for InclusionValidator, ExclusionValidator, and FormatValidator [Prem Sichanugrist] @@ -13,8 +15,39 @@ * ActiveModel::AttributeMethods allows attributes to be defined on demand [Alexander Uvarov] +* Add support for selectively enabling/disabling observers [Myron Marston] + + +*Rails 3.0.7 (April 18, 2011)* + +*No changes. + + +*Rails 3.0.6 (April 5, 2011) + +* Fix when database column name has some symbolic characters (e.g. Oracle CASE# VARCHAR2(20)) #5818 #6850 [Robert Pankowecki, Santiago Pastorino] + +* Fix length validation for fixnums #6556 [Andriy Tyurnikov] + +* Fix i18n key collision with namespaced models #6448 [yves.senn] + + +*Rails 3.0.5 (February 26, 2011)* + +* No changes. + + +*Rails 3.0.4 (February 8, 2011)* + +* No changes. + + +*Rails 3.0.3 (November 16, 2010)* + +* No changes. + -*Rails 3.0.2 (unreleased)* +*Rails 3.0.2 (November 15, 2010)* * No changes diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb index 01eef762fd..483b577681 100644 --- a/activemodel/lib/active_model/mass_assignment_security.rb +++ b/activemodel/lib/active_model/mass_assignment_security.rb @@ -35,17 +35,17 @@ module ActiveModel # protected # # def account_params - # scope = admin ? :admin : :default - # sanitize_for_mass_assignment(params[:account], scope) + # role = admin ? :admin : :default + # sanitize_for_mass_assignment(params[:account], role) # end # # end # module ClassMethods # Attributes named in this macro are protected from mass-assignment - # whenever attributes are sanitized before assignment. A scope for the - # attributes is optional, if no scope is provided then :default is used. - # A scope can be defined by using the :as option. + # whenever attributes are sanitized before assignment. A role for the + # attributes is optional, if no role is provided then :default is used. + # A role can be defined by using the :as option. # # Mass-assignment to these attributes will simply be ignored, to assign # to them you can use direct writer methods. This is meant to protect @@ -67,7 +67,7 @@ module ActiveModel # end # end # - # When using a :default scope : + # When using the :default role : # # customer = Customer.new # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default) @@ -78,7 +78,7 @@ module ActiveModel # customer.credit_rating = "Average" # customer.credit_rating # => "Average" # - # And using the :admin scope : + # And using the :admin role : # # customer = Customer.new # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin) @@ -93,10 +93,10 @@ module ActiveModel # to sanitize attributes won't provide sufficient protection. def attr_protected(*args) options = args.extract_options! - scope = options[:as] || :default + role = options[:as] || :default self._protected_attributes = protected_attributes_configs.dup - self._protected_attributes[scope] = self.protected_attributes(scope) + args + self._protected_attributes[role] = self.protected_attributes(role) + args self._active_authorizer = self._protected_attributes end @@ -104,8 +104,8 @@ module ActiveModel # Specifies a white list of model attributes that can be set via # mass-assignment. # - # Like +attr_protected+, a scope for the attributes is optional, - # if no scope is provided then :default is used. A scope can be defined by + # Like +attr_protected+, a role for the attributes is optional, + # if no role is provided then :default is used. A role can be defined by # using the :as option. # # This is the opposite of the +attr_protected+ macro: Mass-assignment @@ -131,7 +131,7 @@ module ActiveModel # end # end # - # When using a :default scope : + # When using the :default role : # # customer = Customer.new # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default) @@ -141,7 +141,7 @@ module ActiveModel # customer.credit_rating = "Average" # customer.credit_rating # => "Average" # - # And using the :admin scope : + # And using the :admin role : # # customer = Customer.new # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin) @@ -152,20 +152,20 @@ module ActiveModel # to sanitize attributes won't provide sufficient protection. def attr_accessible(*args) options = args.extract_options! - scope = options[:as] || :default + role = options[:as] || :default self._accessible_attributes = accessible_attributes_configs.dup - self._accessible_attributes[scope] = self.accessible_attributes(scope) + args + self._accessible_attributes[role] = self.accessible_attributes(role) + args self._active_authorizer = self._accessible_attributes end - def protected_attributes(scope = :default) - protected_attributes_configs[scope] + def protected_attributes(role = :default) + protected_attributes_configs[role] end - def accessible_attributes(scope = :default) - accessible_attributes_configs[scope] + def accessible_attributes(role = :default) + accessible_attributes_configs[role] end def active_authorizers @@ -198,12 +198,12 @@ module ActiveModel protected - def sanitize_for_mass_assignment(attributes, scope = :default) - mass_assignment_authorizer(scope).sanitize(attributes) + def sanitize_for_mass_assignment(attributes, role = :default) + mass_assignment_authorizer(role).sanitize(attributes) end - def mass_assignment_authorizer(scope = :default) - self.class.active_authorizer[scope] + def mass_assignment_authorizer(role = :default) + self.class.active_authorizer[role] end end end diff --git a/activemodel/lib/active_model/observer_array.rb b/activemodel/lib/active_model/observer_array.rb index ab7f86007f..5fb73f1c78 100644 --- a/activemodel/lib/active_model/observer_array.rb +++ b/activemodel/lib/active_model/observer_array.rb @@ -2,7 +2,7 @@ require 'set' module ActiveModel # Stores the enabled/disabled state of individual observers for - # a particular model classes. + # a particular model class. class ObserverArray < Array attr_reader :model_class def initialize(model_class, *args) @@ -10,14 +10,57 @@ module ActiveModel super(*args) end + # Returns true if the given observer is disabled for the model class. def disabled_for?(observer) disabled_observers.include?(observer.class) end + # Disables one or more observers. This supports multiple forms: + # + # ORM.observers.disable :user_observer + # # => disables the UserObserver + # + # User.observers.disable AuditTrail + # # => disables the AuditTrail observer for User notifications. + # # Other models will still notify the AuditTrail observer. + # + # ORM.observers.disable :observer_1, :observer_2 + # # => disables Observer1 and Observer2 for all models. + # + # ORM.observers.disable :all + # # => disables all observers for all models. + # + # User.observers.disable :all do + # # all user observers are disabled for + # # just the duration of the block + # end def disable(*observers, &block) set_enablement(false, observers, &block) end + # Enables one or more observers. This supports multiple forms: + # + # ORM.observers.enable :user_observer + # # => enables the UserObserver + # + # User.observers.enable AuditTrail + # # => enables the AuditTrail observer for User notifications. + # # Other models will not be affected (i.e. they will not + # # trigger notifications to AuditTrail if previously disabled) + # + # ORM.observers.enable :observer_1, :observer_2 + # # => enables Observer1 and Observer2 for all models. + # + # ORM.observers.enable :all + # # => enables all observers for all models. + # + # User.observers.enable :all do + # # all user observers are enabled for + # # just the duration of the block + # end + # + # Note: all observers are enabled by default. This method is only + # useful when you have previously disabled one or more observers. def enable(*observers, &block) set_enablement(true, observers, &block) end diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index cfe71074a5..4682ae07ef 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -40,7 +40,11 @@ module ActiveModel observers.replace(values.flatten) end - # Gets the current observers. + # Gets an array of observers observing this model. + # The array also provides +enable+ and +disable+ methods + # that allow you to selectively enable and disable observers. + # (see <tt>ActiveModel::ObserverArray.enable</tt> and + # <tt>ActiveModel::ObserverArray.disable</tt> for more on this) def observers @observers ||= ObserverArray.new(self) end @@ -222,7 +226,8 @@ module ActiveModel self.class.observed_classes end - # Send observed_method(object) if the method exists. + # Send observed_method(object) if the method exists and + # the observer is enabled for the given object's class. def update(observed_method, object) #:nodoc: return unless respond_to?(observed_method) return if disabled_for?(object) diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb index d4295e6afe..19639b1363 100644 --- a/activemodel/lib/active_model/serializers/xml.rb +++ b/activemodel/lib/active_model/serializers/xml.rb @@ -33,6 +33,7 @@ module ActiveModel protected def compute_type + return if value.nil? type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name] type ||= :string if value.respond_to?(:to_str) type ||= :yaml diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb index 23ba42bf35..68c138da84 100644 --- a/activemodel/lib/active_model/version.rb +++ b/activemodel/lib/active_model/version.rb @@ -3,7 +3,7 @@ module ActiveModel MAJOR = 3 MINOR = 1 TINY = 0 - PRE = "beta" + PRE = "beta1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/activemodel/test/cases/mass_assignment_security_test.rb b/activemodel/test/cases/mass_assignment_security_test.rb index b22ce874ea..43a12eed61 100644 --- a/activemodel/test/cases/mass_assignment_security_test.rb +++ b/activemodel/test/cases/mass_assignment_security_test.rb @@ -10,7 +10,7 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase assert_equal expected, sanitized end - def test_only_moderator_scope_attribute_accessible + def test_only_moderator_role_attribute_accessible user = SpecialUser.new expected = { "name" => "John Smith", "email" => "john@smith.com" } sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true), :moderator) @@ -27,7 +27,7 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase assert_equal expected, sanitized end - def test_admin_scoped_attributes_accessible + def test_attributes_accessible_with_admin_role user = Person.new expected = { "name" => "John Smith", "email" => "john@smith.com", "admin" => true } sanitized = user.sanitize_for_mass_assignment(expected.merge("super_powers" => true), :admin) diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb index b6a2f88667..8f5c196850 100644 --- a/activemodel/test/cases/serializers/xml_serialization_test.rb +++ b/activemodel/test/cases/serializers/xml_serialization_test.rb @@ -92,6 +92,10 @@ class XmlSerializationTest < ActiveModel::TestCase test "should serialize string" do assert_match %r{<name>aaron stack</name>}, @contact.to_xml end + + test "should serialize nil" do + assert_match %r{<pseudonyms nil=\"true\"></pseudonyms>}, @contact.to_xml(:methods => :pseudonyms) + end test "should serialize integer" do assert_match %r{<age type="integer">25</age>}, @contact.to_xml diff --git a/activemodel/test/cases/validations/conditional_validation_test.rb b/activemodel/test/cases/validations/conditional_validation_test.rb index 3cb95b4a00..e06b04af19 100644 --- a/activemodel/test/cases/validations/conditional_validation_test.rb +++ b/activemodel/test/cases/validations/conditional_validation_test.rb @@ -11,7 +11,7 @@ class ConditionalValidationTest < ActiveModel::TestCase def test_if_validation_using_method_true # When the method returns true - Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :if => :condition_is_true ) + Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :if => :condition_is_true ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -20,7 +20,7 @@ class ConditionalValidationTest < ActiveModel::TestCase def test_unless_validation_using_method_true # When the method returns true - Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :unless => :condition_is_true ) + Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :unless => :condition_is_true ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? assert t.errors[:title].empty? @@ -28,7 +28,7 @@ class ConditionalValidationTest < ActiveModel::TestCase def test_if_validation_using_method_false # When the method returns false - Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :if => :condition_is_true_but_its_not ) + Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :if => :condition_is_true_but_its_not ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? assert t.errors[:title].empty? @@ -36,7 +36,7 @@ class ConditionalValidationTest < ActiveModel::TestCase def test_unless_validation_using_method_false # When the method returns false - Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :unless => :condition_is_true_but_its_not ) + Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :unless => :condition_is_true_but_its_not ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -45,7 +45,7 @@ class ConditionalValidationTest < ActiveModel::TestCase def test_if_validation_using_string_true # When the evaluated string returns true - Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :if => "a = 1; a == 1" ) + Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :if => "a = 1; a == 1" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -54,7 +54,7 @@ class ConditionalValidationTest < ActiveModel::TestCase def test_unless_validation_using_string_true # When the evaluated string returns true - Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :unless => "a = 1; a == 1" ) + Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :unless => "a = 1; a == 1" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? assert t.errors[:title].empty? @@ -62,7 +62,7 @@ class ConditionalValidationTest < ActiveModel::TestCase def test_if_validation_using_string_false # When the evaluated string returns false - Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :if => "false") + Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :if => "false") t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? assert t.errors[:title].empty? @@ -70,7 +70,7 @@ class ConditionalValidationTest < ActiveModel::TestCase def test_unless_validation_using_string_false # When the evaluated string returns false - Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", :unless => "false") + Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :unless => "false") t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -79,7 +79,7 @@ class ConditionalValidationTest < ActiveModel::TestCase def test_if_validation_using_block_true # When the block returns true - Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", + Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :if => Proc.new { |r| r.content.size > 4 } ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? @@ -89,7 +89,7 @@ class ConditionalValidationTest < ActiveModel::TestCase def test_unless_validation_using_block_true # When the block returns true - Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", + Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :unless => Proc.new { |r| r.content.size > 4 } ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? @@ -98,7 +98,7 @@ class ConditionalValidationTest < ActiveModel::TestCase def test_if_validation_using_block_false # When the block returns false - Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", + Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :if => Proc.new { |r| r.title != "uhohuhoh"} ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? @@ -107,7 +107,7 @@ class ConditionalValidationTest < ActiveModel::TestCase def test_unless_validation_using_block_false # When the block returns false - Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}", + Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :unless => Proc.new { |r| r.title != "uhohuhoh"} ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb index 73647efea5..2ce714fef0 100644 --- a/activemodel/test/cases/validations/format_validation_test.rb +++ b/activemodel/test/cases/validations/format_validation_test.rb @@ -27,7 +27,7 @@ class PresenceValidationTest < ActiveModel::TestCase end def test_validate_format_with_allow_blank - Topic.validates_format_of(:title, :with => /^Validation\smacros \w+!$/, :allow_blank=>true) + Topic.validates_format_of(:title, :with => /^Validation\smacros \w+!$/, :allow_blank => true) assert Topic.new("title" => "Shouldn't be valid").invalid? assert Topic.new("title" => "").valid? assert Topic.new("title" => nil).valid? diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb index 92c473de0e..413da92de4 100644 --- a/activemodel/test/cases/validations/inclusion_validation_test.rb +++ b/activemodel/test/cases/validations/inclusion_validation_test.rb @@ -42,7 +42,7 @@ class InclusionValidationTest < ActiveModel::TestCase end def test_validates_inclusion_of_with_allow_nil - Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :allow_nil=>true ) + Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :allow_nil => true ) assert Topic.new("title" => "a!", "content" => "abc").invalid? assert Topic.new("title" => "", "content" => "abc").invalid? diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb index f02514639b..44048a9c1d 100644 --- a/activemodel/test/cases/validations/length_validation_test.rb +++ b/activemodel/test/cases/validations/length_validation_test.rb @@ -11,7 +11,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_with_allow_nil - Topic.validates_length_of( :title, :is => 5, :allow_nil=>true ) + Topic.validates_length_of( :title, :is => 5, :allow_nil => true ) assert Topic.new("title" => "ab").invalid? assert Topic.new("title" => "").invalid? @@ -20,7 +20,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_with_allow_blank - Topic.validates_length_of( :title, :is => 5, :allow_blank=>true ) + Topic.validates_length_of( :title, :is => 5, :allow_blank => true ) assert Topic.new("title" => "ab").invalid? assert Topic.new("title" => "").valid? @@ -176,16 +176,16 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_nasty_params - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is=>-6) } - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6) } - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum=>"a") } - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :maximum=>"a") } - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>"a") } - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is=>"a") } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is => -6) } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within => 6) } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum => "a") } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, :maximum => "a") } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within => "a") } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is => "a") } end def test_validates_length_of_custom_errors_for_minimum_with_message - Topic.validates_length_of( :title, :minimum=>5, :message=>"boo %{count}" ) + Topic.validates_length_of( :title, :minimum => 5, :message => "boo %{count}" ) t = Topic.new("title" => "uhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -193,7 +193,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_minimum_with_too_short - Topic.validates_length_of( :title, :minimum=>5, :too_short=>"hoo %{count}" ) + Topic.validates_length_of( :title, :minimum => 5, :too_short => "hoo %{count}" ) t = Topic.new("title" => "uhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -201,7 +201,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_maximum_with_message - Topic.validates_length_of( :title, :maximum=>5, :message=>"boo %{count}" ) + Topic.validates_length_of( :title, :maximum => 5, :message => "boo %{count}" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -222,7 +222,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_maximum_with_too_long - Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %{count}" ) + Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -244,7 +244,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_is_with_message - Topic.validates_length_of( :title, :is=>5, :message=>"boo %{count}" ) + Topic.validates_length_of( :title, :is => 5, :message => "boo %{count}" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -252,7 +252,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_is_with_wrong_length - Topic.validates_length_of( :title, :is=>5, :wrong_length=>"hoo %{count}" ) + Topic.validates_length_of( :title, :is => 5, :wrong_length => "hoo %{count}" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -331,7 +331,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_with_block - Topic.validates_length_of :content, :minimum => 5, :too_short=>"Your essay must be at least %{count} words.", + Topic.validates_length_of :content, :minimum => 5, :too_short => "Your essay must be at least %{count} words.", :tokenizer => lambda {|str| str.scan(/\w+/) } t = Topic.new(:content => "this content should be long enough") assert t.valid? diff --git a/activemodel/test/models/contact.rb b/activemodel/test/models/contact.rb index f4f3078473..7bfc542afb 100644 --- a/activemodel/test/models/contact.rb +++ b/activemodel/test/models/contact.rb @@ -16,6 +16,10 @@ class Contact options.each { |name, value| send("#{name}=", value) } end + def pseudonyms + nil + end + def persisted? id end diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 9ff29f1155..32bcf02139 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,24 @@ *Rails 3.1.0 (unreleased)* +* CSV Fixtures are deprecated and support will be removed in Rails 3.2.0 + +* AR#new, AR#create, AR#create!, AR#update_attributes and AR#update_attributes! all accept a second hash as option that allows you + to specify which role to consider when assigning attributes. This is built on top of ActiveModel's + new mass assignment capabilities: + + class Post < ActiveRecord::Base + attr_accessible :title + attr_accessible :title, :published_at, :as => :admin + end + + Post.new(params[:post], :as => :admin) + + assign_attributes() with similar API was also added and attributes=(params, guard) was deprecated. + + Please note that this changes the method signatures for AR#new, AR#create, AR#create!, AR#update_attributes and AR#update_attributes!. If you have overwritten these methods you should update them accordingly. + + [Josh Kalderimis] + * default_scope can take a block, lambda, or any other object which responds to `call` for lazy evaluation: @@ -22,25 +41,7 @@ [Jon Leighton] -* Calling 'default_scope' multiple times in a class (including when a superclass calls - 'default_scope') is deprecated. The current behavior is that this will merge the default - scopes together: - - class Post < ActiveRecord::Base # Rails 3.1 - default_scope where(:published => true) - default_scope where(:hidden => false) - # The default scope is now: where(:published => true, :hidden => false) - end - - In Rails 3.2, the behavior will be changed to overwrite previous scopes: - - class Post < ActiveRecord::Base # Rails 3.2 - default_scope where(:published => true) - default_scope where(:hidden => false) - # The default scope is now: where(:hidden => false) - end - - If you wish to merge default scopes in special ways, it is recommended to define your default +* If you wish to merge default scopes in special ways, it is recommended to define your default scope as a class method and use the standard techniques for sharing code (inheritance, mixins, etc.): @@ -293,6 +294,84 @@ IrreversibleMigration exception will be raised when going down. [Aaron Patterson] +*Rails 3.0.7 (April 18, 2011)* + +* Destroying records via nested attributes works independent of reject_if LH #6006 [Durran Jordan] + +* Delegate any? and many? to Model.scoped for consistency [Andrew White] + +* Quote the ORDER BY clause in batched finds - fixes #6620 [Andrew White] + +* Change exists? so records are not instantiated - fixes #6127. This prevents after_find + and after_initialize callbacks being triggered when checking for record existence. + [Andrew White] + +* Fix performance bug with attribute accessors which only occurred on Ruby 1.8.7, and ensure we + cache type-casted values when the column returned from the db contains non-standard chars. + [Jon Leighton] + +* Fix a performance regression introduced here 86acbf1cc050c8fa8c74a10c735e467fb6fd7df8 + related to read_attribute method [Stian Grytøyr] + + +*Rails 3.0.6 (April 5, 2011)* + +* Un-deprecate reorder method [Sebastian Martinez] + +* Extensions are applied when calling +except+ or +only+ on relations. + Thanks to Iain Hecker. + +* Schemas set in set_table_name are respected by the mysql adapter. LH #5322 + +* Fixed a bug when empty? was called on a grouped Relation that wasn't loaded. + LH #5829 + +* Reapply extensions when using except and only. Thanks Iain Hecker. + +* Binary data is escaped when being inserted to SQLite3 Databases. Thanks + Naruse! + + +*Rails 3.0.5 (February 26, 2011)* + +* Model.where(:column => 1).where(:column => 2) will always produce an AND +query. + + [Aaron Patterson] + +* Deprecated support for interpolated association conditions in the form of :conditions => 'foo = #{bar}'. + + Instead, you should use a proc, like so: + + Before: + + has_many :things, :conditions => 'foo = #{bar}' + + After: + + has_many :things, :conditions => proc { "foo = #{bar}" } + + Inside the proc, 'self' is the object which is the owner of the association, unless you are + eager loading the association, in which case 'self' is the class which the association is within. + + You can have any "normal" conditions inside the proc, so the following will work too: + + has_many :things, :conditions => proc { ["foo = ?", bar] } + + Previously :insert_sql and :delete_sql on has_and_belongs_to_many association allowed you to call + 'record' to get the record being inserted or deleted. This is now passed as an argument to + the proc. + + [Jon Leighton] + + +*Rails 3.0.4 (February 8, 2011)* + +* Added deprecation warning for has_and_belongs_to_many associations where the join table has + additional attributes other than the keys. Access to these attributes is removed in 3.1. + Please use has_many :through instead. [Jon Leighton] + + *Rails 3.0.3 (November 16, 2010)* * Support find by class like this: Post.where(:name => Post) @@ -329,10 +408,12 @@ IrreversibleMigration exception will be raised when going down. [Aaron Patterson] + *Rails 3.0.1 (October 15, 2010)* * Introduce a fix for CVE-2010-3993 + *Rails 3.0.0 (August 29, 2010)* * Changed update_attribute to not run callbacks and update the record directly in the database [Neeraj Singh] @@ -532,12 +613,12 @@ IrreversibleMigration exception will be raised when going down. * Add Support for updating deeply nested models from a single form. #1202 [Eloy Duran] - class Book < ActiveRecord::Base - has_one :author - has_many :pages + class Book < ActiveRecord::Base + has_one :author + has_many :pages - accepts_nested_attributes_for :author, :pages - end + accepts_nested_attributes_for :author, :pages + end * Make after_save callbacks fire only if the record was successfully saved. #1735 [Michael Lovitt] @@ -957,7 +1038,7 @@ so newlines etc are escaped #10385 [Norbert Crombach] "foo.bar" => "`foo`.`bar`" * Complete the assimilation of Sexy Migrations from ErrFree [Chris Wanstrath, PJ Hyett] - http://errtheblog.com/post/2381 + http://errtheblog.com/post/2381 * Qualified column names work in hash conditions, like :conditions => { 'comments.created_at' => ... }. #9733 [Jack Danger Canty] @@ -1073,7 +1154,7 @@ single-table inheritance. #3833, #9886 [Gabriel Gironda, rramdas, François Bea * Improve performance and functionality of the postgresql adapter. Closes #8049 [roderickvd] - For more information see: http://dev.rubyonrails.org/ticket/8049 + For more information see: http://dev.rubyonrails.org/ticket/8049 * Don't clobber includes passed to has_many.count [Jack Danger Canty] @@ -1583,8 +1664,8 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing] * Added support for conditions on Base.exists? #5689 [Josh Peek]. Examples: assert (Topic.exists?(:author_name => "David")) - assert (Topic.exists?(:author_name => "Mary", :approved => true)) - assert (Topic.exists?(["parent_id = ?", 1])) + assert (Topic.exists?(:author_name => "Mary", :approved => true)) + assert (Topic.exists?(["parent_id = ?", 1])) * Schema dumper quotes date :default values. [Dave Thomas] @@ -2040,8 +2121,8 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing] * Added support for conditions on Base.exists? #5689 [Josh Peek]. Examples: assert (Topic.exists?(:author_name => "David")) - assert (Topic.exists?(:author_name => "Mary", :approved => true)) - assert (Topic.exists?(["parent_id = ?", 1])) + assert (Topic.exists?(:author_name => "Mary", :approved => true)) + assert (Topic.exists?(["parent_id = ?", 1])) * Schema dumper quotes date :default values. [Dave Thomas] diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc index a27640eac9..3a89446a83 100644 --- a/activerecord/README.rdoc +++ b/activerecord/README.rdoc @@ -219,4 +219,4 @@ API documentation is at Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here: -* https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets +* https://github.com/rails/rails/issues diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index 9d4cbbc150..3a5035305b 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -23,5 +23,5 @@ Gem::Specification.new do |s| s.add_dependency('activesupport', version) s.add_dependency('activemodel', version) s.add_dependency('arel', '~> 2.1.0') - s.add_dependency('tzinfo', '~> 0.3.23') + s.add_dependency('tzinfo', '~> 0.3.27') end diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb index 06a414b874..62d48d3a2c 100644 --- a/activerecord/lib/active_record/associations/builder/singular_association.rb +++ b/activerecord/lib/active_record/associations/builder/singular_association.rb @@ -13,6 +13,19 @@ module ActiveRecord::Associations::Builder private + def define_readers + super + name = self.name + + model.redefine_method("#{name}_loaded?") do + ActiveSupport::Deprecation.warn( + "Calling obj.#{name}_loaded? is deprecated. Please use " \ + "obj.association(:#{name}).loaded? instead." + ) + association(name).loaded? + end + end + def define_constructors name = self.name diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 6cdec8c487..4429c655a2 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -4,7 +4,7 @@ module ActiveRecord module Associations # = Active Record Association Collection # - # AssociationCollection is an abstract class that provides common stuff to + # CollectionAssociation is an abstract class that provides common stuff to # ease the implementation of association proxies that represent # collections. See the class hierarchy in AssociationProxy. # @@ -94,7 +94,13 @@ module ActiveRecord end def build(attributes = {}, options = {}, &block) - build_or_create(:build, attributes, options, &block) + if attributes.is_a?(Array) + attributes.collect { |attr| build(attr, options, &block) } + else + add_to_target(build_record(attributes, options)) do |record| + yield(record) if block_given? + end + end end def create(attributes = {}, options = {}, &block) @@ -102,7 +108,16 @@ module ActiveRecord raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved" end - build_or_create(:create, attributes, options, &block) + if attributes.is_a?(Array) + attributes.collect { |attr| create(attr, options, &block) } + else + transaction do + add_to_target(build_record(attributes, options)) do |record| + yield(record) if block_given? + insert_record(record) + end + end + end end def create!(attrs = {}, options = {}, &block) @@ -321,15 +336,7 @@ module ActiveRecord def load_target if find_target? - targets = [] - - begin - targets = find_target - rescue ActiveRecord::RecordNotFound - reset - end - - @target = merge_target_lists(targets, target) + @target = merge_target_lists(find_target, target) end loaded! @@ -337,20 +344,18 @@ module ActiveRecord end def add_to_target(record) - transaction do - callback(:before_add, record) - yield(record) if block_given? + callback(:before_add, record) + yield(record) if block_given? - if options[:uniq] && index = @target.index(record) - @target[index] = record - else - @target << record - end - - callback(:after_add, record) - set_inverse_instance(record) + if options[:uniq] && index = @target.index(record) + @target[index] = record + else + @target << record end + callback(:after_add, record) + set_inverse_instance(record) + record end @@ -374,7 +379,7 @@ module ActiveRecord if options[:finder_sql] reflection.klass.find_by_sql(custom_finder_sql) else - find(:all) + scoped.all end records = options[:uniq] ? uniq(records) : records @@ -403,26 +408,16 @@ module ActiveRecord end + existing end - def build_or_create(method, attributes, options) - records = Array.wrap(attributes).map do |attrs| - record = build_record(attrs, options) - - add_to_target(record) do - yield(record) if block_given? - insert_record(record) if method == :create - end - end - - attributes.is_a?(Array) ? records : records.first - end - # Do the relevant stuff to insert the given record into the association collection. def insert_record(record, validate = true) raise NotImplementedError end def build_record(attributes, options) - reflection.build_association(scoped.scope_for_create.merge(attributes), options) + record = reflection.build_association + record.assign_attributes(scoped.scope_for_create, :without_protection => true) + record.assign_attributes(attributes, options) + record end def delete_or_destroy(records, method) diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 388173c1fb..adfc71d435 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -64,9 +64,12 @@ module ActiveRecord def method_missing(method, *args, &block) match = DynamicFinderMatch.match(method) - if match && match.creator? - attributes = match.attribute_names - return send(:"find_by_#{attributes.join('_and_')}", *args) || create(Hash[attributes.zip(args)]) + if match && match.instantiator? + record = send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r| + @association.send :set_owner_attributes, r + @association.send :add_to_target, r + yield(r) if block_given? + end end if target.respond_to?(method) || (!@association.klass.respond_to?(method) && Class.respond_to?(method)) diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 7134dc85c8..2f3a6e71f1 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -10,7 +10,7 @@ module ActiveRecord reflection.klass.transaction do if target && target != record - remove_target!(options[:dependent]) + remove_target!(options[:dependent]) unless target.destroyed? end if record diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb index 0a666598ed..c32753782f 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -91,12 +91,12 @@ module ActiveRecord constraint = build_constraint(reflection, table, key, foreign_table, foreign_key) - relation.from(join(table, constraint)) - unless conditions[i].empty? - relation.where(sanitize(conditions[i], table)) + constraint = constraint.and(sanitize(conditions[i], table)) end + relation.from(join(table, constraint)) + # The current table in this iteration becomes the foreign table in the next foreign_table = table end diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index ea4d73d414..877ddf3ee1 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -18,7 +18,7 @@ module ActiveRecord end def create(attributes = {}, options = {}) - new_record(:create, attributes, options) + build(attributes, options).tap { |record| record.save } end def create!(attributes = {}, options = {}) @@ -26,7 +26,14 @@ module ActiveRecord end def build(attributes = {}, options = {}) - new_record(:build, attributes, options) + record = reflection.build_association + record.assign_attributes( + scoped.scope_for_create.except(klass.primary_key), + :without_protection => true + ) + record.assign_attributes(attributes, options) + set_new_record(record) + record end private @@ -43,13 +50,6 @@ module ActiveRecord def set_new_record(record) replace(record) end - - def new_record(method, attributes, options) - attributes = scoped.scope_for_create.merge(attributes || {}) - record = reflection.send("#{method}_association", attributes, options) - set_new_record(record) - record - end end end end diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb index e6ab628719..53c5c3cedf 100644 --- a/activerecord/lib/active_record/associations/through_association.rb +++ b/activerecord/lib/active_record/associations/through_association.rb @@ -14,7 +14,10 @@ module ActiveRecord def target_scope scope = super chain[1..-1].each do |reflection| - scope = scope.merge(reflection.klass.scoped) + scope = scope.merge( + reflection.klass.scoped.with_default_scope. + except(:select, :create_with) + ) end scope end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 58a056bce9..e1bf2ccc8a 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -482,7 +482,7 @@ module ActiveRecord #:nodoc: # # Create a single new object # User.create(:first_name => 'Jamie') # - # # Create a single new object using the :admin mass-assignment security scope + # # Create a single new object using the :admin mass-assignment security role # User.create({ :first_name => 'Jamie', :is_admin => true }, :as => :admin) # # # Create a single new object bypassing mass-assignment security @@ -830,6 +830,10 @@ module ActiveRecord #:nodoc: @symbolized_base_class ||= base_class.to_s.to_sym end + def symbolized_sti_name + @symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class + end + # Returns the base AR subclass that this class descends from. If A # extends AR::Base, A.base_class will return A. If B descends from A # through some arbitrarily deep hierarchy, B.base_class will return A. @@ -891,7 +895,7 @@ module ActiveRecord #:nodoc: # not use the default_scope: # # Post.unscoped { - # limit(10) # Fires "SELECT * FROM posts LIMIT 10" + # Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10" # } # # It is recommended to use block form of unscoped because chaining unscoped with <tt>scope</tt> @@ -1482,7 +1486,7 @@ MSG # # Instantiates a single new object # User.new(:first_name => 'Jamie') # - # # Instantiates a single new object using the :admin mass-assignment security scope + # # Instantiates a single new object using the :admin mass-assignment security role # User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin) # # # Instantiates a single new object bypassing mass-assignment security @@ -1648,17 +1652,16 @@ MSG return unless new_attributes.is_a?(Hash) - guard_protected_attributes ||= true - if guard_protected_attributes - assign_attributes(new_attributes) - else + if guard_protected_attributes == false assign_attributes(new_attributes, :without_protection => true) + else + assign_attributes(new_attributes) end end # Allows you to set all the attributes for a particular mass-assignment - # security scope by passing in a hash of attributes with keys matching - # the attribute names (which again matches the column names) and the scope + # security role by passing in a hash of attributes with keys matching + # the attribute names (which again matches the column names) and the role # name using the :as option. # # To bypass mass-assignment security you can use the :without_protection => true @@ -1684,13 +1687,15 @@ MSG # user.name # => "Josh" # user.is_admin? # => true def assign_attributes(new_attributes, options = {}) + return unless new_attributes + attributes = new_attributes.stringify_keys - scope = options[:as] || :default + role = options[:as] || :default multi_parameter_attributes = [] unless options[:without_protection] - attributes = sanitize_for_mass_assignment(attributes, scope) + attributes = sanitize_for_mass_assignment(attributes, role) end attributes.each do |k, v| @@ -1943,32 +1948,9 @@ MSG errors = [] callstack.each do |name, values_with_empty_parameters| begin - klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass - # in order to allow a date to be set without a year, we must keep the empty values. - # Otherwise, we wouldn't be able to distinguish it from a date with an empty day. - values = values_with_empty_parameters.reject { |v| v.nil? } - - if values.empty? - send(name + "=", nil) - else - - value = if Time == klass - instantiate_time_object(name, values) - elsif Date == klass - begin - values = values_with_empty_parameters.collect do |v| v.nil? ? 1 : v end - Date.new(*values) - rescue ArgumentError => ex # if Date.new raises an exception on an invalid date - instantiate_time_object(name, values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates - end - else - klass.new(*values) - end - - send(name + "=", value) - end + send(name + "=", read_value_from_parameter(name, values_with_empty_parameters)) rescue => ex - errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name) + errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name}", ex, name) end end unless errors.empty? @@ -1976,19 +1958,65 @@ MSG end end + def read_value_from_parameter(name, values_hash_from_param) + klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass + if values_hash_from_param.values.all?{|v|v.nil?} + nil + elsif klass == Time + read_time_parameter_value(name, values_hash_from_param) + elsif klass == Date + read_date_parameter_value(name, values_hash_from_param) + else + read_other_parameter_value(klass, name, values_hash_from_param) + end + end + + def read_time_parameter_value(name, values_hash_from_param) + # If Date bits were not provided, error + raise "Missing Parameter" if [1,2,3].any?{|position| !values_hash_from_param.has_key?(position)} + max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6) + set_values = (1..max_position).collect{|position| values_hash_from_param[position] } + # If Date bits were provided but blank, then default to 1 + # If Time bits are not there, then default to 0 + [1,1,1,0,0,0].each_with_index{|v,i| set_values[i] = set_values[i].blank? ? v : set_values[i]} + instantiate_time_object(name, set_values) + end + + def read_date_parameter_value(name, values_hash_from_param) + set_values = (1..3).collect{|position| values_hash_from_param[position].blank? ? 1 : values_hash_from_param[position]} + begin + Date.new(*set_values) + rescue ArgumentError => ex # if Date.new raises an exception on an invalid date + instantiate_time_object(name, set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates + end + end + + def read_other_parameter_value(klass, name, values_hash_from_param) + max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param) + values = (1..max_position).collect do |position| + raise "Missing Parameter" if !values_hash_from_param.has_key?(position) + values_hash_from_param[position] + end + klass.new(*values) + end + + def extract_max_param_for_multiparameter_attributes(values_hash_from_param, upper_cap = 100) + [values_hash_from_param.keys.max,upper_cap].min + end + def extract_callstack_for_multiparameter_attributes(pairs) attributes = { } for pair in pairs multiparameter_name, value = pair attribute_name = multiparameter_name.split("(").first - attributes[attribute_name] = [] unless attributes.include?(attribute_name) + attributes[attribute_name] = {} unless attributes.include?(attribute_name) parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value) - attributes[attribute_name] << [ find_parameter_position(multiparameter_name), parameter_value ] + attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value end - attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } } + attributes end def type_cast_attribute_value(multiparameter_name, value) @@ -1996,7 +2024,7 @@ MSG end def find_parameter_position(multiparameter_name) - multiparameter_name.scan(/\(([0-9]*).*\)/).first.first + multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i end # Returns a comma-separated pair list, like "key1 = val1, key2 = val2". diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 3045e30407..b3eb23bbb3 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -6,15 +6,7 @@ module ActiveRecord # Returns an array of record hashes with the column names as keys and # column values as values. def select_all(sql, name = nil, binds = []) - if supports_statement_cache? - select(sql, name, binds) - else - return select(sql, name) if binds.empty? - binds = binds.dup - select sql.gsub('?') { - quote(*binds.shift.reverse) - }, name - end + select(sql, name, binds) end # Returns a record hash with the column names as keys and column values diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 8af22fe9f5..ac2da73a84 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -184,6 +184,10 @@ module ActiveRecord QUOTED_FALSE end + def substitute_at(column, index) + Arel.sql "\0" + end + # REFERENTIAL INTEGRITY ==================================== def disable_referential_integrity(&block) #:nodoc: @@ -292,14 +296,14 @@ module ActiveRecord binds = binds.dup # Pretend to support bind parameters - execute sql.gsub('?') { quote(*binds.shift.reverse) }, name + execute sql.gsub("\0") { quote(*binds.shift.reverse) }, name end def exec_delete(sql, name, binds) binds = binds.dup # Pretend to support bind parameters - execute sql.gsub('?') { quote(*binds.shift.reverse) }, name + execute sql.gsub("\0") { quote(*binds.shift.reverse) }, name @connection.affected_rows end alias :exec_update :exec_delete @@ -646,7 +650,8 @@ module ActiveRecord # Returns an array of record hashes with the column names as keys and # column values as values. def select(sql, name = nil, binds = []) - exec_query(sql, name, binds).to_a + binds = binds.dup + exec_query(sql.gsub("\0") { quote(*binds.shift.reverse) }, name).to_a end def exec_query(sql, name = 'SQL', binds = []) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 37db2be7a9..f4beeceb61 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -39,6 +39,16 @@ module ActiveRecord # :stopdoc: class << self attr_accessor :money_precision + def string_to_time(string) + return string unless String === string + + case string + when 'infinity' then 1.0 / 0.0 + when '-infinity' then -1.0 / 0.0 + else + super + end + end end # :startdoc: @@ -349,6 +359,9 @@ module ActiveRecord return super unless column case value + when Float + return super unless value.infinite? && column.type == :datetime + "'#{value.to_s.downcase}'" when Numeric return super unless column.sql_type == 'money' # Not truly string input, so doesn't require (or allow) escape string syntax. diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 96fea741e0..4aa6389a04 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -13,6 +13,7 @@ require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/logger' require 'active_support/ordered_hash' +require 'active_support/core_ext/module/deprecation' if defined? ActiveRecord class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc: @@ -28,11 +29,9 @@ class FixturesFileNotFound < StandardError; end # # = Fixture formats # -# Fixtures come in 3 flavors: +# Fixtures come in 1 flavor: # # 1. YAML fixtures -# 2. CSV fixtures -# 3. Single-file fixtures # # == YAML fixtures # @@ -74,56 +73,6 @@ class FixturesFileNotFound < StandardError; end # parent_id: 1 # title: Child # -# == CSV fixtures -# -# Fixtures can also be kept in the Comma Separated Value (CSV) format. Akin to YAML fixtures, CSV fixtures are stored -# in a single file, but instead end with the <tt>.csv</tt> file extension -# (Rails example: <tt><your-rails-app>/test/fixtures/web_sites.csv</tt>). -# -# The format of this type of fixture file is much more compact than the others, but also a little harder to read by us -# humans. The first line of the CSV file is a comma-separated list of field names. The rest of the -# file is then comprised -# of the actual data (1 per line). Here's an example: -# -# id, name, url -# 1, Ruby On Rails, http://www.rubyonrails.org -# 2, Google, http://www.google.com -# -# Should you have a piece of data with a comma character in it, you can place double quotes around that value. If you -# need to use a double quote character, you must escape it with another double quote. -# -# Another unique attribute of the CSV fixture is that it has *no* fixture name like the other two formats. Instead, the -# fixture names are automatically generated by deriving the class name of the fixture file and adding an incrementing -# number to the end. In our example, the 1st fixture would be called "web_site_1" and the 2nd one would be called -# "web_site_2". -# -# Most databases and spreadsheets support exporting to CSV format, so this is a great format for you to choose if you -# have existing data somewhere already. -# -# == Single-file fixtures -# -# This type of fixture was the original format for Active Record that has since been deprecated in -# favor of the YAML and CSV formats. -# Fixtures for this format are created by placing text files in a sub-directory (with the name of the model) -# to the directory appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically -# configured for Rails, so you can just put your files in <tt><your-rails-app>/test/fixtures/<your-model-name>/</tt> -- -# like <tt><your-rails-app>/test/fixtures/web_sites/</tt> for the WebSite model). -# -# Each text file placed in this directory represents a "record". Usually these types of fixtures are named without -# extensions, but if you are on a Windows machine, you might consider adding <tt>.txt</tt> as the extension. -# Here's what the above example might look like: -# -# web_sites/google -# web_sites/yahoo.txt -# web_sites/ruby-on-rails -# -# The file format of a standard fixture is simple. Each line is a property (or column in db speak) and has the syntax -# of "name => value". Here's an example of the ruby-on-rails fixture above: -# -# id => 1 -# name => Ruby on Rails -# url => http://www.rubyonrails.org -# # = Using fixtures in testcases # # Since fixtures are a testing construct, we use them in our unit and functional tests. There are two ways to use the @@ -176,7 +125,7 @@ class FixturesFileNotFound < StandardError; end # = Dynamic fixtures with ERB # # Some times you don't care about the content of the fixtures as much as you care about the volume. In these cases, you can -# mix ERB in with your YAML or CSV fixtures to create a bunch of fixtures for load testing, like: +# mix ERB in with your YAML fixtures to create a bunch of fixtures for load testing, like: # # <% for i in 1..1000 %> # fix_<%= i %>: @@ -423,8 +372,8 @@ class FixturesFileNotFound < StandardError; end # to the rescue: # # george_reginald: -# monkey_id: <%= Fixtures.identify(:reginald) %> -# pirate_id: <%= Fixtures.identify(:george) %> +# monkey_id: <%= ActiveRecord::Fixtures.identify(:reginald) %> +# pirate_id: <%= ActiveRecord::Fixtures.identify(:george) %> # # == Support for YAML defaults # @@ -444,369 +393,375 @@ class FixturesFileNotFound < StandardError; end # # Any fixture labeled "DEFAULTS" is safely ignored. -class Fixtures - MAX_ID = 2 ** 30 - 1 +Fixture = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Fixture', 'ActiveRecord::Fixture') +Fixtures = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Fixtures', 'ActiveRecord::Fixtures') - @@all_cached_fixtures = Hash.new { |h,k| h[k] = {} } +module ActiveRecord + class Fixtures + MAX_ID = 2 ** 30 - 1 - def self.find_table_name(table_name) # :nodoc: - ActiveRecord::Base.pluralize_table_names ? - table_name.to_s.singularize.camelize : - table_name.to_s.camelize - end + @@all_cached_fixtures = Hash.new { |h,k| h[k] = {} } - def self.reset_cache - @@all_cached_fixtures.clear - end + def self.find_table_name(table_name) # :nodoc: + ActiveRecord::Base.pluralize_table_names ? + table_name.to_s.singularize.camelize : + table_name.to_s.camelize + end - def self.cache_for_connection(connection) - @@all_cached_fixtures[connection] - end + def self.reset_cache + @@all_cached_fixtures.clear + end - def self.fixture_is_cached?(connection, table_name) - cache_for_connection(connection)[table_name] - end + def self.cache_for_connection(connection) + @@all_cached_fixtures[connection] + end - def self.cached_fixtures(connection, keys_to_fetch = nil) - if keys_to_fetch - cache_for_connection(connection).values_at(*keys_to_fetch) - else - cache_for_connection(connection).values + def self.fixture_is_cached?(connection, table_name) + cache_for_connection(connection)[table_name] end - end - def self.cache_fixtures(connection, fixtures_map) - cache_for_connection(connection).update(fixtures_map) - end + def self.cached_fixtures(connection, keys_to_fetch = nil) + if keys_to_fetch + cache_for_connection(connection).values_at(*keys_to_fetch) + else + cache_for_connection(connection).values + end + end + + def self.cache_fixtures(connection, fixtures_map) + cache_for_connection(connection).update(fixtures_map) + end - def self.instantiate_fixtures(object, fixture_name, fixtures, load_instances = true) - if load_instances - fixtures.each do |name, fixture| - begin - object.instance_variable_set "@#{name}", fixture.find - rescue FixtureClassNotFound - nil + def self.instantiate_fixtures(object, fixture_name, fixtures, load_instances = true) + if load_instances + fixtures.each do |name, fixture| + begin + object.instance_variable_set "@#{name}", fixture.find + rescue FixtureClassNotFound + nil + end end end end - end - def self.instantiate_all_loaded_fixtures(object, load_instances = true) - all_loaded_fixtures.each do |table_name, fixtures| - Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances) + def self.instantiate_all_loaded_fixtures(object, load_instances = true) + all_loaded_fixtures.each do |table_name, fixtures| + ActiveRecord::Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances) + end end - end - cattr_accessor :all_loaded_fixtures - self.all_loaded_fixtures = {} + cattr_accessor :all_loaded_fixtures + self.all_loaded_fixtures = {} - def self.create_fixtures(fixtures_directory, table_names, class_names = {}) - table_names = [table_names].flatten.map { |n| n.to_s } - table_names.each { |n| - class_names[n.tr('/', '_').to_sym] = n.classify if n.include?('/') - } + def self.create_fixtures(fixtures_directory, table_names, class_names = {}) + table_names = [table_names].flatten.map { |n| n.to_s } + table_names.each { |n| + class_names[n.tr('/', '_').to_sym] = n.classify if n.include?('/') + } - # FIXME: Apparently JK uses this. - connection = block_given? ? yield : ActiveRecord::Base.connection + # FIXME: Apparently JK uses this. + connection = block_given? ? yield : ActiveRecord::Base.connection - files_to_read = table_names.reject { |table_name| - fixture_is_cached?(connection, table_name) - } + files_to_read = table_names.reject { |table_name| + fixture_is_cached?(connection, table_name) + } - unless files_to_read.empty? - connection.disable_referential_integrity do - fixtures_map = {} + unless files_to_read.empty? + connection.disable_referential_integrity do + fixtures_map = {} - fixture_files = files_to_read.map do |path| - table_name = path.tr '/', '_' + fixture_files = files_to_read.map do |path| + table_name = path.tr '/', '_' - fixtures_map[path] = Fixtures.new( - connection, - table_name, - class_names[table_name.to_sym] || table_name.classify, - File.join(fixtures_directory, path)) - end + fixtures_map[path] = ActiveRecord::Fixtures.new( + connection, + table_name, + class_names[table_name.to_sym] || table_name.classify, + File.join(fixtures_directory, path)) + end - all_loaded_fixtures.update(fixtures_map) + all_loaded_fixtures.update(fixtures_map) - connection.transaction(:requires_new => true) do - fixture_files.each do |ff| - conn = ff.model_class.respond_to?(:connection) ? ff.model_class.connection : connection - table_rows = ff.table_rows + connection.transaction(:requires_new => true) do + fixture_files.each do |ff| + conn = ff.model_class.respond_to?(:connection) ? ff.model_class.connection : connection + table_rows = ff.table_rows - table_rows.keys.each do |table| - conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete' - end + table_rows.keys.each do |table| + conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete' + end - table_rows.each do |table_name,rows| - rows.each do |row| - conn.insert_fixture(row, table_name) + table_rows.each do |table_name,rows| + rows.each do |row| + conn.insert_fixture(row, table_name) + end end end - end - # Cap primary key sequences to max(pk). - if connection.respond_to?(:reset_pk_sequence!) - table_names.each do |table_name| - connection.reset_pk_sequence!(table_name.tr('/', '_')) + # Cap primary key sequences to max(pk). + if connection.respond_to?(:reset_pk_sequence!) + table_names.each do |table_name| + connection.reset_pk_sequence!(table_name.tr('/', '_')) + end end end - end - cache_fixtures(connection, fixtures_map) + cache_fixtures(connection, fixtures_map) + end end + cached_fixtures(connection, table_names) end - cached_fixtures(connection, table_names) - end - - # Returns a consistent, platform-independent identifier for +label+. - # Identifiers are positive integers less than 2^32. - def self.identify(label) - Zlib.crc32(label.to_s) % MAX_ID - end - attr_reader :table_name, :name, :fixtures, :model_class - - def initialize(connection, table_name, class_name, fixture_path) - @connection = connection - @table_name = table_name - @fixture_path = fixture_path - @name = table_name # preserve fixture base name - @class_name = class_name - - @fixtures = ActiveSupport::OrderedHash.new - @table_name = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}" - - # Should be an AR::Base type class - if class_name.is_a?(Class) - @table_name = class_name.table_name - @connection = class_name.connection - @model_class = class_name - else - @model_class = class_name.constantize rescue nil + # Returns a consistent, platform-independent identifier for +label+. + # Identifiers are positive integers less than 2^32. + def self.identify(label) + Zlib.crc32(label.to_s) % MAX_ID end - read_fixture_files - end + attr_reader :table_name, :name, :fixtures, :model_class - def [](x) - fixtures[x] - end + def initialize(connection, table_name, class_name, fixture_path) + @connection = connection + @table_name = table_name + @fixture_path = fixture_path + @name = table_name # preserve fixture base name + @class_name = class_name - def []=(k,v) - fixtures[k] = v - end + @fixtures = ActiveSupport::OrderedHash.new + @table_name = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}" - def each(&block) - fixtures.each(&block) - end + # Should be an AR::Base type class + if class_name.is_a?(Class) + @table_name = class_name.table_name + @connection = class_name.connection + @model_class = class_name + else + @model_class = class_name.constantize rescue nil + end - def size - fixtures.size - end + read_fixture_files + end - # Return a hash of rows to be inserted. The key is the table, the value is - # a list of rows to insert to that table. - def table_rows - now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now - now = now.to_s(:db) + def [](x) + fixtures[x] + end - # allow a standard key to be used for doing defaults in YAML - fixtures.delete('DEFAULTS') + def []=(k,v) + fixtures[k] = v + end - # track any join tables we need to insert later - rows = Hash.new { |h,table| h[table] = [] } + def each(&block) + fixtures.each(&block) + end - rows[table_name] = fixtures.map do |label, fixture| - row = fixture.to_hash + def size + fixtures.size + end - if model_class && model_class < ActiveRecord::Base - # 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| - row[name] = now unless row.key?(name) - end - end + # Return a hash of rows to be inserted. The key is the table, the value is + # a list of rows to insert to that table. + def table_rows + now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now + now = now.to_s(:db) - # interpolate the fixture label - row.each do |key, value| - row[key] = label if value == "$LABEL" - end + # allow a standard key to be used for doing defaults in YAML + fixtures.delete('DEFAULTS') - # generate a primary key if necessary - if has_primary_key_column? && !row.include?(primary_key_name) - row[primary_key_name] = Fixtures.identify(label) - end + # track any join tables we need to insert later + rows = Hash.new { |h,table| h[table] = [] } + + rows[table_name] = fixtures.map do |label, fixture| + row = fixture.to_hash - # If STI is used, find the correct subclass for association reflection - reflection_class = - if row.include?(inheritance_column_name) - row[inheritance_column_name].constantize rescue model_class - else - model_class + if model_class && model_class < ActiveRecord::Base + # 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| + row[name] = now unless row.key?(name) + end end - reflection_class.reflect_on_all_associations.each do |association| - case association.macro - when :belongs_to - # Do not replace association name with association foreign key if they are named the same - fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s + # interpolate the fixture label + row.each do |key, value| + row[key] = label if value == "$LABEL" + end - if association.name.to_s != fk_name && value = row.delete(association.name.to_s) - if association.options[:polymorphic] && value.sub!(/\s*\(([^\)]*)\)\s*$/, "") - # support polymorphic belongs_to as "label (Type)" - row[association.foreign_type] = $1 - end + # generate a primary key if necessary + if has_primary_key_column? && !row.include?(primary_key_name) + row[primary_key_name] = ActiveRecord::Fixtures.identify(label) + end - row[fk_name] = Fixtures.identify(value) + # If STI is used, find the correct subclass for association reflection + reflection_class = + if row.include?(inheritance_column_name) + row[inheritance_column_name].constantize rescue model_class + else + model_class end - when :has_and_belongs_to_many - if (targets = row.delete(association.name.to_s)) - targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/) - table_name = association.options[:join_table] - rows[table_name].concat targets.map { |target| - { association.foreign_key => row[primary_key_name], - association.association_foreign_key => Fixtures.identify(target) } - } + + reflection_class.reflect_on_all_associations.each do |association| + case association.macro + when :belongs_to + # Do not replace association name with association foreign key if they are named the same + fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s + + if association.name.to_s != fk_name && value = row.delete(association.name.to_s) + if association.options[:polymorphic] && value.sub!(/\s*\(([^\)]*)\)\s*$/, "") + # support polymorphic belongs_to as "label (Type)" + row[association.foreign_type] = $1 + end + + row[fk_name] = ActiveRecord::Fixtures.identify(value) + end + when :has_and_belongs_to_many + if (targets = row.delete(association.name.to_s)) + targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/) + table_name = association.options[:join_table] + rows[table_name].concat targets.map { |target| + { association.foreign_key => row[primary_key_name], + association.association_foreign_key => ActiveRecord::Fixtures.identify(target) } + } + end end end end - end - - row - end - rows - end - private - def primary_key_name - @primary_key_name ||= model_class && model_class.primary_key + row + end + rows end - def has_primary_key_column? - @has_primary_key_column ||= primary_key_name && - model_class.columns.any? { |c| c.name == primary_key_name } - end + private + def primary_key_name + @primary_key_name ||= model_class && model_class.primary_key + end - def timestamp_column_names - @timestamp_column_names ||= - %w(created_at created_on updated_at updated_on) & column_names - end + def has_primary_key_column? + @has_primary_key_column ||= primary_key_name && + model_class.columns.any? { |c| c.name == primary_key_name } + end - def inheritance_column_name - @inheritance_column_name ||= model_class && model_class.inheritance_column - end + def timestamp_column_names + @timestamp_column_names ||= + %w(created_at created_on updated_at updated_on) & column_names + end - def column_names - @column_names ||= @connection.columns(@table_name).collect { |c| c.name } - end + def inheritance_column_name + @inheritance_column_name ||= model_class && model_class.inheritance_column + end - def read_fixture_files - if File.file?(yaml_file_path) - read_yaml_fixture_files - elsif File.file?(csv_file_path) - read_csv_fixture_files - else - raise FixturesFileNotFound, "Could not find #{yaml_file_path} or #{csv_file_path}" + def column_names + @column_names ||= @connection.columns(@table_name).collect { |c| c.name } end - end - def read_yaml_fixture_files - yaml_string = (Dir["#{@fixture_path}/**/*.yml"].select { |f| - File.file?(f) - } + [yaml_file_path]).map { |file_path| IO.read(file_path) }.join - - if yaml = parse_yaml_string(yaml_string) - # If the file is an ordered map, extract its children. - yaml_value = - if yaml.respond_to?(:type_id) && yaml.respond_to?(:value) - yaml.value - else - [yaml] - end + def read_fixture_files + if File.file?(yaml_file_path) + read_yaml_fixture_files + elsif File.file?(csv_file_path) + read_csv_fixture_files + else + raise FixturesFileNotFound, "Could not find #{yaml_file_path} or #{csv_file_path}" + end + end - yaml_value.each do |fixture| - raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each) - fixture.each do |name, data| - unless data - raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)" + def read_yaml_fixture_files + yaml_string = (Dir["#{@fixture_path}/**/*.yml"].select { |f| + File.file?(f) + } + [yaml_file_path]).map { |file_path| IO.read(file_path) }.join + + if yaml = parse_yaml_string(yaml_string) + # If the file is an ordered map, extract its children. + yaml_value = + if yaml.respond_to?(:type_id) && yaml.respond_to?(:value) + yaml.value + else + [yaml] end - fixtures[name] = Fixture.new(data, model_class) + yaml_value.each do |fixture| + raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each) + fixture.each do |name, data| + unless data + raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)" + end + + fixtures[name] = ActiveRecord::Fixture.new(data, model_class) + end end end end - end - def read_csv_fixture_files - reader = CSV.parse(erb_render(IO.read(csv_file_path))) - header = reader.shift - i = 0 - reader.each do |row| - data = {} - row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip } - fixtures["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class) + def read_csv_fixture_files + reader = CSV.parse(erb_render(IO.read(csv_file_path))) + header = reader.shift + i = 0 + reader.each do |row| + data = {} + row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip } + fixtures["#{@class_name.to_s.underscore}_#{i+=1}"] = ActiveRecord::Fixture.new(data, model_class) + end end - end + deprecate :read_csv_fixture_files - def yaml_file_path - "#{@fixture_path}.yml" - end + def yaml_file_path + "#{@fixture_path}.yml" + end - def csv_file_path - @fixture_path + ".csv" - end + def csv_file_path + @fixture_path + ".csv" + end - def yaml_fixtures_key(path) - File.basename(@fixture_path).split(".").first - end + def yaml_fixtures_key(path) + File.basename(@fixture_path).split(".").first + end - def parse_yaml_string(fixture_content) - YAML::load(erb_render(fixture_content)) - rescue => error - raise Fixture::FormatError, "a YAML error occurred parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}" - end + def parse_yaml_string(fixture_content) + YAML::load(erb_render(fixture_content)) + rescue => error + raise Fixture::FormatError, "a YAML error occurred parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}" + end - def erb_render(fixture_content) - ERB.new(fixture_content).result - end -end + def erb_render(fixture_content) + ERB.new(fixture_content).result + end + end -class Fixture #:nodoc: - include Enumerable + class Fixture #:nodoc: + include Enumerable - class FixtureError < StandardError #:nodoc: - end + class FixtureError < StandardError #:nodoc: + end - class FormatError < FixtureError #:nodoc: - end + class FormatError < FixtureError #:nodoc: + end - attr_reader :model_class, :fixture + attr_reader :model_class, :fixture - def initialize(fixture, model_class) - @fixture = fixture - @model_class = model_class - end + def initialize(fixture, model_class) + @fixture = fixture + @model_class = model_class + end - def class_name - model_class.name if model_class - end + def class_name + model_class.name if model_class + end - def each - fixture.each { |item| yield item } - end + def each + fixture.each { |item| yield item } + end - def [](key) - fixture[key] - end + def [](key) + fixture[key] + end - alias :to_hash :fixture + alias :to_hash :fixture - def find - if model_class - model_class.find(fixture[model_class.primary_key]) - else - raise FixtureClassNotFound, "No class attached to find." + def find + if model_class + model_class.find(fixture[model_class.primary_key]) + else + raise FixtureClassNotFound, "No class attached to find." + end end end end @@ -832,7 +787,7 @@ module ActiveRecord self.pre_loaded_fixtures = false self.fixture_class_names = Hash.new do |h, table_name| - h[table_name] = Fixtures.find_table_name(table_name) + h[table_name] = ActiveRecord::Fixtures.find_table_name(table_name) end end @@ -944,7 +899,7 @@ module ActiveRecord ActiveRecord::Base.connection.begin_db_transaction # Load fixtures for every test. else - Fixtures.reset_cache + ActiveRecord::Fixtures.reset_cache @@already_loaded_fixtures[self.class] = nil @loaded_fixtures = load_fixtures end @@ -957,7 +912,7 @@ module ActiveRecord return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank? unless run_in_transaction? - Fixtures.reset_cache + ActiveRecord::Fixtures.reset_cache end # Rollback changes if a transaction is active. @@ -970,7 +925,7 @@ module ActiveRecord private def load_fixtures - fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names) + fixtures = ActiveRecord::Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names) Hash[fixtures.map { |f| [f.name, f] }] end @@ -979,16 +934,16 @@ module ActiveRecord def instantiate_fixtures if pre_loaded_fixtures - raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty? + raise RuntimeError, 'Load fixtures before instantiating them.' if ActiveRecord::Fixtures.all_loaded_fixtures.empty? unless @@required_fixture_classes - self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys + self.class.require_fixture_classes ActiveRecord::Fixtures.all_loaded_fixtures.keys @@required_fixture_classes = true end - Fixtures.instantiate_all_loaded_fixtures(self, load_instances?) + ActiveRecord::Fixtures.instantiate_all_loaded_fixtures(self, load_instances?) else raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil? @loaded_fixtures.each do |fixture_name, fixtures| - Fixtures.instantiate_fixtures(self, fixture_name, fixtures, load_instances?) + ActiveRecord::Fixtures.instantiate_fixtures(self, fixture_name, fixtures, load_instances?) end end end diff --git a/activerecord/lib/active_record/identity_map.rb b/activerecord/lib/active_record/identity_map.rb index 9eb47ad99f..b15b5a8133 100644 --- a/activerecord/lib/active_record/identity_map.rb +++ b/activerecord/lib/active_record/identity_map.rb @@ -12,10 +12,36 @@ module ActiveRecord # In order to enable IdentityMap, set <tt>config.active_record.identity_map = true</tt> # in your <tt>config/application.rb</tt> file. # - # IdentityMap is disabled by default. + # IdentityMap is disabled by default and still in development (i.e. use it with care). + # + # == Associations + # + # Active Record Identity Map does not track associations yet. For example: + # + # comment = @post.comments.first + # comment.post = nil + # @post.comments.include?(comment) #=> true + # + # Ideally, the example above would return false, removing the comment object from the + # post association when the association is nullified. This may cause side effects, as + # in the situation below, if Identity Map is enabled: + # + # Post.has_many :comments, :dependent => :destroy + # + # comment = @post.comments.first + # comment.post = nil + # comment.save + # Post.destroy(@post.id) + # + # Without using Identity Map, the code above will destroy the @post object leaving + # the comment object intact. However, once we enable Identity Map, the post loaded + # by Post.destroy is exactly the same object as the object @post. As the object @post + # still has the comment object in @post.comments, once Identity Map is enabled, the + # comment object will be accidently removed. + # + # This inconsistency is meant to be fixed in future Rails releases. # module IdentityMap - extend ActiveSupport::Concern class << self def enabled=(flag) @@ -49,11 +75,11 @@ module ActiveRecord end def get(klass, primary_key) - record = repository[klass.symbolized_base_class][primary_key] + record = repository[klass.symbolized_sti_name][primary_key] if record.is_a?(klass) ActiveSupport::Notifications.instrument("identity.active_record", - :line => "From Identity Map (id: #{primary_key})", + :line => "From Identity Map (id: #{primary_key})", :name => "#{klass} Loaded", :connection_id => object_id) @@ -64,15 +90,15 @@ module ActiveRecord end def add(record) - repository[record.class.symbolized_base_class][record.id] = record + repository[record.class.symbolized_sti_name][record.id] = record end def remove(record) - repository[record.class.symbolized_base_class].delete(record.id) + repository[record.class.symbolized_sti_name].delete(record.id) end - def remove_by_id(symbolized_base_class, id) - repository[symbolized_base_class].delete(id) + def remove_by_id(symbolized_sti_name, id) + repository[symbolized_sti_name].delete(id) end def clear diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 9a31675782..cdedcde0eb 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -94,7 +94,7 @@ module ActiveRecord relation = self.class.unscoped stmt = relation.where( - relation.table[self.class.primary_key].eq(quoted_id).and( + relation.table[self.class.primary_key].eq(id).and( relation.table[lock_col].eq(quote_value(previous_lock_value)) ) ).arel.compile_update(arel_attributes_values(false, false, attribute_names)) diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb index 0893d7e337..c723436330 100644 --- a/activerecord/lib/active_record/observer.rb +++ b/activerecord/lib/active_record/observer.rb @@ -110,8 +110,8 @@ module ActiveRecord next unless respond_to?(callback) callback_meth = :"_notify_#{observer_name}_for_#{callback}" unless klass.respond_to?(callback_meth) - klass.send(:define_method, callback_meth) do - observer.send(callback, self) + klass.send(:define_method, callback_meth) do |&block| + observer.send(callback, self, &block) end klass.send(callback, callback_meth) end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index b4531ed35f..b9041f44d8 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -146,7 +146,7 @@ module ActiveRecord # will fail and false will be returned. # # When updating model attributes, mass-assignment security protection is respected. - # If no +:as+ option is supplied then the +:default+ scope will be used. + # If no +:as+ option is supplied then the +:default+ role will be used. # If you want to bypass the protection given by +attr_protected+ and # +attr_accessible+ then you can do so using the +:without_protection+ option. def update_attributes(attributes, options = {}) diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb index 929998eb85..4e61671473 100644 --- a/activerecord/lib/active_record/query_cache.rb +++ b/activerecord/lib/active_record/query_cache.rb @@ -40,6 +40,7 @@ module ActiveRecord def close @target.close if @target.respond_to?(:close) ensure + ActiveRecord::Base.connection.clear_query_cache unless @original_cache_value ActiveRecord::Base.connection.disable_query_cache! end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 5703fac033..85ad43b35f 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -203,18 +203,18 @@ db_namespace = namespace :db do # only files matching "20091231235959_some_name.rb" pattern if match_data = /^(\d{14})_(.+)\.rb$/.match(file) status = db_list.delete(match_data[1]) ? 'up' : 'down' - file_list << [status, match_data[1], match_data[2]] + file_list << [status, match_data[1], match_data[2].humanize] end end + db_list.map! do |version| + ['up', version, '********** NO FILE **********'] + end # output puts "\ndatabase: #{config['database']}\n\n" puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name" puts "-" * 50 - file_list.each do |file| - puts "#{file[0].center(8)} #{file[1].ljust(14)} #{file[2].humanize}" - end - db_list.each do |version| - puts "#{'up'.center(8)} #{version.ljust(14)} *** NO FILE ***" + (db_list + file_list).sort_by {|migration| migration[1]}.each do |migration| + puts "#{migration[0].center(8)} #{migration[1].ljust(14)} #{migration[2]}" end puts end @@ -305,7 +305,7 @@ db_namespace = namespace :db do fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.{yml,csv}"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file| - Fixtures.create_fixtures(fixtures_dir, fixture_file) + ActiveRecord::Fixtures.create_fixtures(fixtures_dir, fixture_file) end end @@ -316,13 +316,13 @@ db_namespace = namespace :db do label, id = ENV['LABEL'], ENV['ID'] raise 'LABEL or ID required' if label.blank? && id.blank? - puts %Q(The fixture ID for "#{label}" is #{Fixtures.identify(label)}.) if label + puts %Q(The fixture ID for "#{label}" is #{ActiveRecord::Fixtures.identify(label)}.) if label base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures') Dir["#{base_dir}/**/*.yml"].each do |file| if data = YAML::load(ERB.new(IO.read(file)).result) data.keys.each do |key| - key_id = Fixtures.identify(key) + key_id = ActiveRecord::Fixtures.identify(key) if key == label || key_id == id.to_i puts "#{file}: #{key} (#{key_id})" diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 869eebfa34..0fcae92d51 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -146,7 +146,7 @@ module ActiveRecord if options.except(:distinct).present? apply_finder_options(options.except(:distinct)).calculate(operation, column_name, :distinct => options[:distinct]) else - if eager_loading? || includes_values.present? + if eager_loading? || (includes_values.present? && references_eager_loaded_tables?) construct_relation_for_association_calculations.calculate(operation, column_name, options) else perform_calculation(operation, column_name, options) @@ -161,21 +161,20 @@ module ActiveRecord def perform_calculation(operation, column_name, options = {}) operation = operation.to_s.downcase - distinct = nil + distinct = options[:distinct] if operation == "count" column_name ||= (select_for_count || :all) unless arel.ast.grep(Arel::Nodes::OuterJoin).empty? distinct = true - column_name = primary_key if column_name == :all end + column_name = primary_key if column_name == :all && distinct + distinct = nil if column_name =~ /\s*DISTINCT\s+/i end - distinct = options[:distinct] || distinct - if @group_values.any? execute_grouped_calculation(operation, column_name, distinct) else @@ -197,7 +196,7 @@ module ActiveRecord def execute_simple_calculation(operation, column_name, distinct) #:nodoc: # Postgresql doesn't like ORDER BY when there are no GROUP BY - relation = except(:order) + relation = reorder(nil) if operation == "count" && (relation.limit_value || relation.offset_value) # Shortcut when limit is zero. diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 57c9921ea8..32d1cff6c3 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -375,7 +375,12 @@ module ActiveRecord if loaded? @records.last else - @last ||= reverse_order.limit(1).to_a[0] + @last ||= + if offset_value || limit_value + to_a.last + else + reverse_order.limit(1).to_a[0] + end end end diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb index 7e77aefb21..c3e976002e 100644 --- a/activerecord/lib/active_record/session_store.rb +++ b/activerecord/lib/active_record/session_store.rb @@ -40,7 +40,7 @@ module ActiveRecord # You must implement these methods: # # self.find_by_session_id(session_id) - # initialize(hash_of_session_id_and_data) + # initialize(hash_of_session_id_and_data, options_hash = {}) # attr_reader :session_id # attr_accessor :data # save @@ -83,6 +83,8 @@ module ActiveRecord cattr_accessor :data_column_name self.data_column_name = 'data' + attr_accessible :session_id, :data, :marshaled_data + before_save :marshal_data! before_save :raise_on_session_data_overflow! @@ -123,7 +125,7 @@ module ActiveRecord end end - def initialize(attributes = nil) + def initialize(attributes = nil, options = {}) @data = nil super end diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb index 29efbbcb8c..0d47eb3338 100644 --- a/activerecord/lib/active_record/test_case.rb +++ b/activerecord/lib/active_record/test_case.rb @@ -13,6 +13,13 @@ module ActiveRecord ActiveRecord::IdentityMap.clear end + # Backport skip to Ruby 1.8. test/unit doesn't support it, so just + # make it a noop. + unless instance_methods.map(&:to_s).include?("skip") + def skip(message) + end + end + def assert_date_from_db(expected, actual, message = nil) # SybaseAdapter doesn't have a separate column type just for dates, # so the time is in the string and incorrectly formatted diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index de36dd20b3..59b6876135 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -32,11 +32,11 @@ module ActiveRecord module ClassMethods # Creates an object just like Base.create but calls <tt>save!</tt> instead of +save+ # so an exception is raised if the record is invalid. - def create!(attributes = nil, &block) + def create!(attributes = nil, options = {}, &block) if attributes.is_a?(Array) - attributes.collect { |attr| create!(attr, &block) } + attributes.collect { |attr| create!(attr, options, &block) } else - object = new(attributes) + object = new(attributes, options) yield(object) if block_given? object.save! object diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb index 0667be7d23..2c20dd997f 100644 --- a/activerecord/lib/active_record/version.rb +++ b/activerecord/lib/active_record/version.rb @@ -3,7 +3,7 @@ module ActiveRecord MAJOR = 3 MINOR = 1 TINY = 0 - PRE = "beta" + PRE = "beta1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb index 43015098c9..292c7efebb 100644 --- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb @@ -138,7 +138,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase private # custom fixture loader, uses Fixtures#create_fixtures and appends base_path to the current file's path def create_test_fixtures(*fixture_names) - Fixtures.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names) + ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names) end # custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name diff --git a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb new file mode 100644 index 0000000000..cd9c1041dc --- /dev/null +++ b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb @@ -0,0 +1,50 @@ +require "cases/helper" +require 'models/topic' + +module ActiveRecord + module ConnectionAdapters + class Mysql2Adapter + class BindParameterTest < ActiveRecord::TestCase + fixtures :topics + + def test_update_question_marks + str = "foo?bar" + x = Topic.find :first + x.title = str + x.content = str + x.save! + x.reload + assert_equal str, x.title + assert_equal str, x.content + end + + def test_create_question_marks + str = "foo?bar" + x = Topic.create!(:title => str, :content => str) + x.reload + assert_equal str, x.title + assert_equal str, x.content + end + + def test_update_null_bytes + str = "foo\0bar" + x = Topic.find :first + x.title = str + x.content = str + x.save! + x.reload + assert_equal str, x.title + assert_equal str, x.content + end + + def test_create_null_bytes + str = "foo\0bar" + x = Topic.create!(:title => str, :content => str) + x.reload + assert_equal str, x.title + assert_equal str, x.content + end + end + end + end +end diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb index 1efa7deaeb..752b864818 100644 --- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb @@ -138,7 +138,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase private # custom fixture loader, uses Fixtures#create_fixtures and appends base_path to the current file's path def create_test_fixtures(*fixture_names) - Fixtures.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names) + ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names) end # custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index 39e8a7960a..49d8722aff 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -51,7 +51,9 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase categories = Category.joins(:categorizations).includes([{:posts=>:comments}, :authors]) assert_nothing_raised do - assert_equal 3, categories.count + assert_equal 4, categories.count + assert_equal 4, categories.all.count + assert_equal 3, categories.count(:distinct => true) assert_equal 3, categories.all.uniq.size # Must uniq since instantiating with inner joins will get dupes end end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 007f11b535..b149f5912f 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -66,6 +66,63 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 'exotic', bulb.name end + def test_create_from_association_with_nil_values_should_work + car = Car.create(:name => 'honda') + + bulb = car.bulbs.new(nil) + assert_equal 'defaulty', bulb.name + + bulb = car.bulbs.build(nil) + assert_equal 'defaulty', bulb.name + + bulb = car.bulbs.create(nil) + assert_equal 'defaulty', bulb.name + end + + def test_association_keys_bypass_attribute_protection + car = Car.create(:name => 'honda') + + bulb = car.bulbs.new + assert_equal car.id, bulb.car_id + + bulb = car.bulbs.new :car_id => car.id + 1 + assert_equal car.id, bulb.car_id + + bulb = car.bulbs.build + assert_equal car.id, bulb.car_id + + bulb = car.bulbs.build :car_id => car.id + 1 + assert_equal car.id, bulb.car_id + + bulb = car.bulbs.create + assert_equal car.id, bulb.car_id + + bulb = car.bulbs.create :car_id => car.id + 1 + assert_equal car.id, bulb.car_id + end + + def test_association_conditions_bypass_attribute_protection + car = Car.create(:name => 'honda') + + bulb = car.frickinawesome_bulbs.new + assert_equal true, bulb.frickinawesome? + + bulb = car.frickinawesome_bulbs.new(:frickinawesome => false) + assert_equal true, bulb.frickinawesome? + + bulb = car.frickinawesome_bulbs.build + assert_equal true, bulb.frickinawesome? + + bulb = car.frickinawesome_bulbs.build(:frickinawesome => false) + assert_equal true, bulb.frickinawesome? + + bulb = car.frickinawesome_bulbs.create + assert_equal true, bulb.frickinawesome? + + bulb = car.frickinawesome_bulbs.create(:frickinawesome => false) + assert_equal true, bulb.frickinawesome? + end + # When creating objects on the association, we must not do it within a scope (even though it # would be convenient), because this would cause that scope to be applied to any callbacks etc. def test_build_and_create_should_not_happen_within_scope @@ -605,6 +662,30 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size end + def test_find_or_initialize_updates_collection_size + number_of_clients = companies(:first_firm).clients_of_firm.size + companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client") + assert_equal number_of_clients + 1, companies(:first_firm).clients_of_firm.size + end + + def test_find_or_create_with_hash + post = authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody') + assert_equal post, authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody') + assert post.persisted? + end + + def test_find_or_create_with_one_attribute_followed_by_hash + post = authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody') + assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody') + assert post.persisted? + end + + def test_find_or_create_should_work_with_block + post = authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'} + assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'} + assert post.persisted? + end + def test_deleting force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first) diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index f3c96ccbe6..356a4a7a09 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -4,6 +4,7 @@ require 'models/project' require 'models/company' require 'models/ship' require 'models/pirate' +require 'models/car' require 'models/bulb' class HasOneAssociationsTest < ActiveRecord::TestCase @@ -95,6 +96,15 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_nil Account.find(old_account_id).firm_id end + def test_natural_assignment_to_nil_after_destroy + firm = companies(:rails_core) + old_account_id = firm.account.id + firm.account.destroy + firm.account = nil + assert_nil companies(:rails_core).account + assert_raise(ActiveRecord::RecordNotFound) { Account.find(old_account_id) } + end + def test_association_change_calls_delete companies(:first_firm).deletable_account = Account.new(:credit_limit => 5) assert_equal [], Account.destroyed_account_ids[companies(:first_firm).id] @@ -359,4 +369,45 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_equal pirate.id, ships(:black_pearl).reload.pirate_id assert_nil new_ship.pirate_id end + + def test_deprecated_association_loaded + firm = companies(:first_firm) + firm.association(:account).stubs(:loaded?).returns(stub) + + assert_deprecated do + assert_equal firm.association(:account).loaded?, firm.account_loaded? + end + end + + def test_association_keys_bypass_attribute_protection + car = Car.create(:name => 'honda') + + bulb = car.build_bulb + assert_equal car.id, bulb.car_id + + bulb = car.build_bulb :car_id => car.id + 1 + assert_equal car.id, bulb.car_id + + bulb = car.create_bulb + assert_equal car.id, bulb.car_id + + bulb = car.create_bulb :car_id => car.id + 1 + assert_equal car.id, bulb.car_id + end + + def test_association_conditions_bypass_attribute_protection + car = Car.create(:name => 'honda') + + bulb = car.build_frickinawesome_bulb + assert_equal true, bulb.frickinawesome? + + bulb = car.build_frickinawesome_bulb(:frickinawesome => false) + assert_equal true, bulb.frickinawesome? + + bulb = car.create_frickinawesome_bulb + assert_equal true, bulb.frickinawesome? + + bulb = car.create_frickinawesome_bulb(:frickinawesome => false) + assert_equal true, bulb.frickinawesome? + end end diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb index 968025ade8..2503349c08 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -310,4 +310,8 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase assert_equal dashboard, minivan.dashboard assert_equal dashboard, minivan.speedometer.dashboard end + + def test_has_one_through_with_custom_select_on_join_model_default_scope + assert_equal clubs(:boring_club), members(:groucho).selected_club + end end diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb index e2228228a3..55d9a328a7 100644 --- a/activerecord/test/cases/associations/inner_join_association_test.rb +++ b/activerecord/test/cases/associations/inner_join_association_test.rb @@ -2,6 +2,7 @@ require "cases/helper" require 'models/post' require 'models/comment' require 'models/author' +require 'models/essay' require 'models/category' require 'models/categorization' require 'models/person' @@ -34,6 +35,17 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase assert_no_match(/JOIN/i, sql) end + def test_join_conditions_added_to_join_clause + sql = Author.joins(:essays).to_sql + assert_match(/writer_type.*?=.*?Author/i, sql) + assert_no_match(/WHERE/i, sql) + end + + def test_join_conditions_allow_nil_associations + authors = Author.includes(:essays).where(:essays => {:id => nil}) + assert_equal 2, authors.count + end + def test_find_with_implicit_inner_joins_honors_readonly_without_select authors = Author.joins(:posts).to_a assert !authors.empty?, "expected authors to be non-empty" diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index 04f628a398..49d82ba2df 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -85,7 +85,7 @@ class AssociationsTest < ActiveRecord::TestCase def test_should_construct_new_finder_sql_after_create person = Person.new :first_name => 'clark' - assert_equal [], person.readers.find(:all) + assert_equal [], person.readers.all person.save! reader = Reader.create! :person => person, :post => Post.new(:title => "foo", :body => "bar") assert person.readers.find(reader.id) diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 5ee3b2d776..9bc04ed29c 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -45,6 +45,10 @@ class ReadonlyTitlePost < Post attr_readonly :title end +class ProtectedTitlePost < Post + attr_protected :title +end + class Weird < ActiveRecord::Base; end class Boolean < ActiveRecord::Base; end @@ -105,7 +109,7 @@ class BasicsTest < ActiveRecord::TestCase def test_select_symbol topic_ids = Topic.select(:id).map(&:id).sort - assert_equal Topic.find(:all).map(&:id).sort, topic_ids + assert_equal Topic.all.map(&:id).sort, topic_ids end def test_table_exists @@ -491,8 +495,9 @@ class BasicsTest < ActiveRecord::TestCase def test_attributes_guard_protected_attributes_is_deprecated attributes = { "title" => "An amazing title" } - topic = Topic.new - assert_deprecated { topic.send(:attributes=, attributes, false) } + post = ProtectedTitlePost.new + assert_deprecated { post.send(:attributes=, attributes, false) } + assert_equal "An amazing title", post.title end def test_multiparameter_attributes_on_date @@ -575,6 +580,29 @@ class BasicsTest < ActiveRecord::TestCase assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on end + def test_multiparameter_attributes_on_time_with_no_date + ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do + attributes = { + "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00" + } + topic = Topic.find(1) + topic.attributes = attributes + end + assert_equal("written_on", ex.errors[0].attribute) + end + + def test_multiparameter_attributes_on_time_with_invalid_time_params + ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do + attributes = { + "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", + "written_on(4i)" => "2004", "written_on(5i)" => "36", "written_on(6i)" => "64", + } + topic = Topic.find(1) + topic.attributes = attributes + end + assert_equal("written_on", ex.errors[0].attribute) + end + def test_multiparameter_attributes_on_time_with_old_date attributes = { "written_on(1i)" => "1850", "written_on(2i)" => "6", "written_on(3i)" => "24", @@ -586,6 +614,82 @@ class BasicsTest < ActiveRecord::TestCase assert_equal "1850-06-24 16:24:00", topic.written_on.to_s(:db) end + def test_multiparameter_attributes_on_time_will_raise_on_big_time_if_missing_date_parts + ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do + attributes = { + "written_on(4i)" => "16", "written_on(5i)" => "24" + } + topic = Topic.find(1) + topic.attributes = attributes + end + assert_equal("written_on", ex.errors[0].attribute) + end + + def test_multiparameter_attributes_on_time_with_raise_on_small_time_if_missing_date_parts + ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do + attributes = { + "written_on(4i)" => "16", "written_on(5i)" => "12", "written_on(6i)" => "02" + } + topic = Topic.find(1) + topic.attributes = attributes + end + assert_equal("written_on", ex.errors[0].attribute) + end + + def test_multiparameter_attributes_on_time_will_ignore_hour_if_missing + attributes = { + "written_on(1i)" => "2004", "written_on(2i)" => "12", "written_on(3i)" => "12", + "written_on(5i)" => "12", "written_on(6i)" => "02" + } + topic = Topic.find(1) + topic.attributes = attributes + assert_equal Time.local(2004, 12, 12, 0, 12, 2), topic.written_on + end + + def test_multiparameter_attributes_on_time_will_ignore_hour_if_blank + attributes = { + "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "", + "written_on(4i)" => "", "written_on(5i)" => "12", "written_on(6i)" => "02" + } + topic = Topic.find(1) + topic.attributes = attributes + assert_equal 1, topic.written_on.year + assert_equal 1, topic.written_on.month + assert_equal 1, topic.written_on.day + assert_equal 0, topic.written_on.hour + assert_equal 12, topic.written_on.min + assert_equal 2, topic.written_on.sec + end + + def test_multiparameter_attributes_on_time_will_ignore_date_if_empty + attributes = { + "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "", + "written_on(4i)" => "16", "written_on(5i)" => "24" + } + topic = Topic.find(1) + topic.attributes = attributes + assert_equal 1, topic.written_on.year + assert_equal 1, topic.written_on.month + assert_equal 1, topic.written_on.day + assert_equal 16, topic.written_on.hour + assert_equal 24, topic.written_on.min + assert_equal 0, topic.written_on.sec + end + def test_multiparameter_attributes_on_time_with_seconds_will_ignore_date_if_empty + attributes = { + "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "", + "written_on(4i)" => "16", "written_on(5i)" => "12", "written_on(6i)" => "02" + } + topic = Topic.find(1) + topic.attributes = attributes + assert_equal 1, topic.written_on.year + assert_equal 1, topic.written_on.month + assert_equal 1, topic.written_on.day + assert_equal 16, topic.written_on.hour + assert_equal 12, topic.written_on.min + assert_equal 02, topic.written_on.sec + end + def test_multiparameter_attributes_on_time_with_utc ActiveRecord::Base.default_timezone = :utc attributes = { @@ -692,6 +796,42 @@ class BasicsTest < ActiveRecord::TestCase assert_equal address, customer.address end + def test_multiparameter_assignment_of_aggregation_out_of_order + customer = Customer.new + address = Address.new("The Street", "The City", "The Country") + attributes = { "address(3)" => address.country, "address(2)" => address.city, "address(1)" => address.street } + customer.attributes = attributes + assert_equal address, customer.address + end + + def test_multiparameter_assignment_of_aggregation_with_missing_values + ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do + customer = Customer.new + address = Address.new("The Street", "The City", "The Country") + attributes = { "address(2)" => address.city, "address(3)" => address.country } + customer.attributes = attributes + end + assert_equal("address", ex.errors[0].attribute) + end + + def test_multiparameter_assignment_of_aggregation_with_blank_values + customer = Customer.new + address = Address.new("The Street", "The City", "The Country") + attributes = { "address(1)" => "", "address(2)" => address.city, "address(3)" => address.country } + customer.attributes = attributes + assert_equal Address.new(nil, "The City", "The Country"), customer.address + end + + def test_multiparameter_assignment_of_aggregation_with_large_index + ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do + customer = Customer.new + address = Address.new("The Street", "The City", "The Country") + attributes = { "address(1)" => "The Street", "address(2)" => address.city, "address(3000)" => address.country } + customer.attributes = attributes + end + assert_equal("address", ex.errors[0].attribute) + end + def test_attributes_on_dummy_time # Oracle, and Sybase do not have a TIME datatype. return true if current_adapter?(:OracleAdapter, :SybaseAdapter) diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 654c4c9010..56f6d795b6 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -319,6 +319,17 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal 4, Account.count(:distinct => true, :include => :firm, :select => :credit_limit) end + def test_should_not_perform_joined_include_by_default + assert_equal Account.count, Account.includes(:firm).count + queries = assert_sql { Account.includes(:firm).count } + assert_no_match(/join/i, queries.last) + end + + def test_should_perform_joined_include_when_referencing_included_tables + joined_count = Account.includes(:firm).where(:companies => {:name => '37signals'}).count + assert_equal 1, joined_count + end + def test_should_count_scoped_select Account.update_all("credit_limit = NULL") assert_equal 0, Account.scoped(:select => "credit_limit").count diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index be4ba18555..4e75eafe3d 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -683,6 +683,27 @@ class FinderTest < ActiveRecord::TestCase assert_nil Topic.find_last_by_title_and_author_name(topic.title, "Anonymous") end + def test_find_last_with_limit_gives_same_result_when_loaded_and_unloaded + scope = Topic.limit(2) + unloaded_last = scope.last + loaded_last = scope.all.last + assert_equal loaded_last, unloaded_last + end + + def test_find_last_with_limit_and_offset_gives_same_result_when_loaded_and_unloaded + scope = Topic.offset(2).limit(2) + unloaded_last = scope.last + loaded_last = scope.all.last + assert_equal loaded_last, unloaded_last + end + + def test_find_last_with_offset_gives_same_result_when_loaded_and_unloaded + scope = Topic.offset(3) + unloaded_last = scope.last + loaded_last = scope.all.last + assert_equal loaded_last, unloaded_last + end + def test_find_all_by_one_attribute topics = Topic.find_all_by_content("Have a nice day") assert_equal 2, topics.size diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 3e20155210..b0bd9c5763 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -36,7 +36,7 @@ class FixturesTest < ActiveRecord::TestCase FIXTURES.each do |name| fixtures = nil assert_nothing_raised { fixtures = create_fixtures(name).first } - assert_kind_of(Fixtures, fixtures) + assert_kind_of(ActiveRecord::Fixtures, fixtures) fixtures.each { |_name, fixture| fixture.each { |key, value| assert_match(MATCH_ATTRIBUTE_NAME, key) @@ -46,7 +46,7 @@ class FixturesTest < ActiveRecord::TestCase end def test_create_fixtures - Fixtures.create_fixtures(FIXTURES_ROOT, "parrots") + ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT, "parrots") assert Parrot.find_by_name('Curious George'), 'George is in the database' end @@ -54,7 +54,7 @@ class FixturesTest < ActiveRecord::TestCase fixtures_array = nil assert_nothing_raised { fixtures_array = create_fixtures(*FIXTURES) } assert_kind_of(Array, fixtures_array) - fixtures_array.each { |fixtures| assert_kind_of(Fixtures, fixtures) } + fixtures_array.each { |fixtures| assert_kind_of(ActiveRecord::Fixtures, fixtures) } end def test_attributes @@ -75,7 +75,7 @@ class FixturesTest < ActiveRecord::TestCase if ActiveRecord::Base.connection.supports_migrations? def test_inserts_with_pre_and_suffix # Reset cache to make finds on the new table work - Fixtures.reset_cache + ActiveRecord::Fixtures.reset_cache ActiveRecord::Base.connection.create_table :prefix_topics_suffix do |t| t.column :title, :string @@ -150,11 +150,11 @@ class FixturesTest < ActiveRecord::TestCase end def test_empty_yaml_fixture - assert_not_nil Fixtures.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/yml/accounts") + assert_not_nil ActiveRecord::Fixtures.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/yml/accounts") end def test_empty_yaml_fixture_with_a_comment_in_it - assert_not_nil Fixtures.new( Account.connection, "companies", 'Company', FIXTURES_ROOT + "/naked/yml/companies") + assert_not_nil ActiveRecord::Fixtures.new( Account.connection, "companies", 'Company', FIXTURES_ROOT + "/naked/yml/companies") end def test_nonexistent_fixture_file @@ -164,23 +164,25 @@ class FixturesTest < ActiveRecord::TestCase assert Dir[nonexistent_fixture_path+"*"].empty? assert_raise(FixturesFileNotFound) do - Fixtures.new( Account.connection, "companies", 'Company', nonexistent_fixture_path) + ActiveRecord::Fixtures.new( Account.connection, "companies", 'Company', nonexistent_fixture_path) end end def test_dirty_dirty_yaml_file - assert_raise(Fixture::FormatError) do - Fixtures.new( Account.connection, "courses", 'Course', FIXTURES_ROOT + "/naked/yml/courses") + assert_raise(ActiveRecord::Fixture::FormatError) do + ActiveRecord::Fixtures.new( Account.connection, "courses", 'Course', FIXTURES_ROOT + "/naked/yml/courses") end end def test_empty_csv_fixtures - assert_not_nil Fixtures.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/csv/accounts") + assert_deprecated do + assert_not_nil ActiveRecord::Fixtures.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/csv/accounts") + end end def test_omap_fixtures assert_nothing_raised do - fixtures = Fixtures.new(Account.connection, 'categories', 'Category', FIXTURES_ROOT + "/categories_ordered") + fixtures = ActiveRecord::Fixtures.new(Account.connection, 'categories', 'Category', FIXTURES_ROOT + "/categories_ordered") i = 0 fixtures.each do |name, fixture| @@ -220,7 +222,7 @@ if Account.connection.respond_to?(:reset_pk_sequence!) def setup @instances = [Account.new(:credit_limit => 50), Company.new(:name => 'RoR Consulting')] - Fixtures.reset_cache # make sure tables get reinitialized + ActiveRecord::Fixtures.reset_cache # make sure tables get reinitialized end def test_resets_to_min_pk_with_specified_pk_and_sequence @@ -524,13 +526,13 @@ class FasterFixturesTest < ActiveRecord::TestCase def load_extra_fixture(name) fixture = create_fixtures(name).first - assert fixture.is_a?(Fixtures) + assert fixture.is_a?(ActiveRecord::Fixtures) @loaded_fixtures[fixture.table_name] = fixture end def test_cache - assert Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'categories') - assert Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'authors') + assert ActiveRecord::Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'categories') + assert ActiveRecord::Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'authors') assert_no_queries do create_fixtures('categories') @@ -538,7 +540,7 @@ class FasterFixturesTest < ActiveRecord::TestCase end load_extra_fixture('posts') - assert Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'posts') + assert ActiveRecord::Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'posts') self.class.setup_fixture_accessors('posts') assert_equal 'Welcome to the weblog', posts(:welcome).title end @@ -548,17 +550,17 @@ class FoxyFixturesTest < ActiveRecord::TestCase fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers, :developers, :"admin/accounts", :"admin/users" def test_identifies_strings - assert_equal(Fixtures.identify("foo"), Fixtures.identify("foo")) - assert_not_equal(Fixtures.identify("foo"), Fixtures.identify("FOO")) + assert_equal(ActiveRecord::Fixtures.identify("foo"), ActiveRecord::Fixtures.identify("foo")) + assert_not_equal(ActiveRecord::Fixtures.identify("foo"), ActiveRecord::Fixtures.identify("FOO")) end def test_identifies_symbols - assert_equal(Fixtures.identify(:foo), Fixtures.identify(:foo)) + assert_equal(ActiveRecord::Fixtures.identify(:foo), ActiveRecord::Fixtures.identify(:foo)) end def test_identifies_consistently - assert_equal 207281424, Fixtures.identify(:ruby) - assert_equal 1066363776, Fixtures.identify(:sapphire_2) + assert_equal 207281424, ActiveRecord::Fixtures.identify(:ruby) + assert_equal 1066363776, ActiveRecord::Fixtures.identify(:sapphire_2) end TIMESTAMP_COLUMNS = %w(created_at created_on updated_at updated_on) diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index fd20f1b120..fbb4ee6f7b 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -104,7 +104,7 @@ class ActiveSupport::TestCase self.use_transactional_fixtures = true def create_fixtures(*table_names, &block) - Fixtures.create_fixtures(ActiveSupport::TestCase.fixture_path, table_names, fixture_class_names, &block) + ActiveRecord::Fixtures.create_fixtures(ActiveSupport::TestCase.fixture_path, table_names, fixture_class_names, &block) end end diff --git a/activerecord/test/cases/identity_map_test.rb b/activerecord/test/cases/identity_map_test.rb index 649715fbb5..a0e16400d2 100644 --- a/activerecord/test/cases/identity_map_test.rb +++ b/activerecord/test/cases/identity_map_test.rb @@ -129,6 +129,41 @@ class IdentityMapTest < ActiveRecord::TestCase end ############################################################################## + # Tests checking if IM is functioning properly on classes with multiple # + # types of inheritance # + ############################################################################## + + def test_inherited_without_type_attribute_without_identity_map + ActiveRecord::IdentityMap.without do + p1 = DestructivePirate.create!(:catchphrase => "I'm not a regular Pirate") + p2 = Pirate.find(p1.id) + assert_not_same(p1, p2) + end + end + + def test_inherited_with_type_attribute_without_identity_map + ActiveRecord::IdentityMap.without do + c = comments(:sub_special_comment) + c1 = SubSpecialComment.find(c.id) + c2 = Comment.find(c.id) + assert_same(c1.class, c2.class) + end + end + + def test_inherited_without_type_attribute + p1 = DestructivePirate.create!(:catchphrase => "I'm not a regular Pirate") + p2 = Pirate.find(p1.id) + assert_not_same(p1, p2) + end + + def test_inherited_with_type_attribute + c = comments(:sub_special_comment) + c1 = SubSpecialComment.find(c.id) + c2 = Comment.find(c.id) + assert_same(c1, c2) + end + + ############################################################################## # Tests checking dirty attribute behaviour with IM # ############################################################################## diff --git a/activerecord/test/cases/lifecycle_test.rb b/activerecord/test/cases/lifecycle_test.rb index 6cd8494c9e..643e949087 100644 --- a/activerecord/test/cases/lifecycle_test.rb +++ b/activerecord/test/cases/lifecycle_test.rb @@ -107,6 +107,23 @@ class ValidatedCommentObserver < ActiveRecord::Observer end end + +class AroundTopic < Topic +end + +class AroundTopicObserver < ActiveRecord::Observer + observe :around_topic + def topic_ids + @topic_ids ||= [] + end + + def around_save(topic) + topic_ids << topic.id + yield(topic) + topic_ids << topic.id + end +end + class LifecycleTest < ActiveRecord::TestCase fixtures :topics, :developers, :minimalistics @@ -206,6 +223,14 @@ class LifecycleTest < ActiveRecord::TestCase assert_equal developer, SalaryChecker.instance.last_saved end + test "around filter from observer should accept block" do + observer = AroundTopicObserver.instance + topic = AroundTopic.new + topic.save + assert_nil observer.topic_ids.first + assert_not_nil observer.topic_ids.last + end + def test_observer_is_called_once observer = DeveloperObserver.instance # activate observer.calls.clear diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index 636a709924..61baa55027 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -5,6 +5,7 @@ require 'models/job' require 'models/reader' require 'models/legacy_thing' require 'models/reference' +require 'models/string_key_object' class LockWithoutDefault < ActiveRecord::Base; end @@ -18,7 +19,40 @@ class ReadonlyFirstNamePerson < Person end class OptimisticLockingTest < ActiveRecord::TestCase - fixtures :people, :legacy_things, :references + fixtures :people, :legacy_things, :references, :string_key_objects + + def test_non_integer_lock_existing + s1 = StringKeyObject.find("record1") + s2 = StringKeyObject.find("record1") + assert_equal 0, s1.lock_version + assert_equal 0, s2.lock_version + + s1.name = 'updated record' + s1.save! + assert_equal 1, s1.lock_version + assert_equal 0, s2.lock_version + + s2.name = 'doubly updated record' + assert_raise(ActiveRecord::StaleObjectError) { s2.save! } + end + + def test_non_integer_lock_destroy + s1 = StringKeyObject.find("record1") + s2 = StringKeyObject.find("record1") + assert_equal 0, s1.lock_version + assert_equal 0, s2.lock_version + + s1.name = 'updated record' + s1.save! + assert_equal 1, s1.lock_version + assert_equal 0, s2.lock_version + assert_raise(ActiveRecord::StaleObjectError) { s2.destroy } + + assert s1.destroy + assert s1.frozen? + assert s1.destroyed? + assert_raises(ActiveRecord::RecordNotFound) { StringKeyObject.find("record1") } + end def test_lock_existing p1 = Person.find(1) diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb index fbbae99e8b..765033852d 100644 --- a/activerecord/test/cases/mass_assignment_security_test.rb +++ b/activerecord/test/cases/mass_assignment_security_test.rb @@ -87,7 +87,11 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase end end - def test_assign_attributes_uses_default_scope_when_no_scope_is_provided + def test_mass_assigning_does_not_choke_on_nil + Firm.new.assign_attributes(nil) + end + + def test_assign_attributes_uses_default_role_when_no_role_is_provided p = LoosePerson.new p.assign_attributes(attributes_hash) @@ -101,28 +105,28 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase assert_all_attributes(p) end - def test_assign_attributes_with_default_scope_and_attr_protected_attributes + def test_assign_attributes_with_default_role_and_attr_protected_attributes p = LoosePerson.new p.assign_attributes(attributes_hash, :as => :default) assert_default_attributes(p) end - def test_assign_attributes_with_admin_scope_and_attr_protected_attributes + def test_assign_attributes_with_admin_role_and_attr_protected_attributes p = LoosePerson.new p.assign_attributes(attributes_hash, :as => :admin) assert_admin_attributes(p) end - def test_assign_attributes_with_default_scope_and_attr_accessible_attributes + def test_assign_attributes_with_default_role_and_attr_accessible_attributes p = TightPerson.new p.assign_attributes(attributes_hash, :as => :default) assert_default_attributes(p) end - def test_assign_attributes_with_admin_scope_and_attr_accessible_attributes + def test_assign_attributes_with_admin_role_and_attr_accessible_attributes p = TightPerson.new p.assign_attributes(attributes_hash, :as => :admin) @@ -153,30 +157,42 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase assert_default_attributes(p, true) end - def test_new_with_admin_scope_with_attr_accessible_attributes + def test_new_with_admin_role_with_attr_accessible_attributes p = TightPerson.new(attributes_hash, :as => :admin) assert_admin_attributes(p) end - def test_new_with_admin_scope_with_attr_protected_attributes + def test_new_with_admin_role_with_attr_protected_attributes p = LoosePerson.new(attributes_hash, :as => :admin) assert_admin_attributes(p) end - def test_create_with_admin_scope_with_attr_accessible_attributes + def test_create_with_admin_role_with_attr_accessible_attributes p = TightPerson.create(attributes_hash, :as => :admin) assert_admin_attributes(p, true) end - def test_create_with_admin_scope_with_attr_protected_attributes + def test_create_with_admin_role_with_attr_protected_attributes p = LoosePerson.create(attributes_hash, :as => :admin) assert_admin_attributes(p, true) end + def test_create_with_bang_with_admin_role_with_attr_accessible_attributes + p = TightPerson.create!(attributes_hash, :as => :admin) + + assert_admin_attributes(p, true) + end + + def test_create_with_bang_with_admin_role_with_attr_protected_attributes + p = LoosePerson.create!(attributes_hash, :as => :admin) + + assert_admin_attributes(p, true) + end + def test_new_with_without_protection_with_attr_accessible_attributes p = TightPerson.new(attributes_hash, :without_protection => true) @@ -201,6 +217,18 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase assert_all_attributes(p) end + def test_create_with_bang_with_without_protection_with_attr_accessible_attributes + p = TightPerson.create!(attributes_hash, :without_protection => true) + + assert_all_attributes(p) + end + + def test_create_with_bang_with_without_protection_with_attr_protected_attributes + p = LoosePerson.create!(attributes_hash, :without_protection => true) + + assert_all_attributes(p) + end + def test_protection_against_class_attribute_writers [:logger, :configurations, :primary_key_prefix_type, :table_name_prefix, :table_name_suffix, :pluralize_table_names, :default_timezone, :schema_format, :lock_optimistically, :record_timestamps].each do |method| @@ -230,12 +258,12 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase assert_default_attributes(best_friend) end - def test_has_one_build_with_admin_scope_with_attr_protected_attributes + def test_has_one_build_with_admin_role_with_attr_protected_attributes best_friend = @person.build_best_friend(attributes_hash, :as => :admin) assert_admin_attributes(best_friend) end - def test_has_one_build_with_admin_scope_with_attr_accessible_attributes + def test_has_one_build_with_admin_role_with_attr_accessible_attributes best_friend = @person.build_best_friend(attributes_hash, :as => :admin) assert_admin_attributes(best_friend) end @@ -257,12 +285,12 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase assert_default_attributes(best_friend, true) end - def test_has_one_create_with_admin_scope_with_attr_protected_attributes + def test_has_one_create_with_admin_role_with_attr_protected_attributes best_friend = @person.create_best_friend(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_admin_scope_with_attr_accessible_attributes + def test_has_one_create_with_admin_role_with_attr_accessible_attributes best_friend = @person.create_best_friend(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end @@ -284,12 +312,12 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase assert_default_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes + def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes + def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end @@ -318,12 +346,12 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase assert_default_attributes(best_friend) end - def test_has_one_build_with_admin_scope_with_attr_protected_attributes + def test_has_one_build_with_admin_role_with_attr_protected_attributes best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin) assert_admin_attributes(best_friend) end - def test_has_one_build_with_admin_scope_with_attr_accessible_attributes + def test_has_one_build_with_admin_role_with_attr_accessible_attributes best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin) assert_admin_attributes(best_friend) end @@ -345,12 +373,12 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase assert_default_attributes(best_friend, true) end - def test_has_one_create_with_admin_scope_with_attr_protected_attributes + def test_has_one_create_with_admin_role_with_attr_protected_attributes best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_admin_scope_with_attr_accessible_attributes + def test_has_one_create_with_admin_role_with_attr_accessible_attributes best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end @@ -372,12 +400,12 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase assert_default_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes + def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes + def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end @@ -406,12 +434,12 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase assert_default_attributes(best_friend) end - def test_has_one_build_with_admin_scope_with_attr_protected_attributes + def test_has_one_build_with_admin_role_with_attr_protected_attributes best_friend = @person.best_friends.build(attributes_hash, :as => :admin) assert_admin_attributes(best_friend) end - def test_has_one_build_with_admin_scope_with_attr_accessible_attributes + def test_has_one_build_with_admin_role_with_attr_accessible_attributes best_friend = @person.best_friends.build(attributes_hash, :as => :admin) assert_admin_attributes(best_friend) end @@ -433,12 +461,12 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase assert_default_attributes(best_friend, true) end - def test_has_one_create_with_admin_scope_with_attr_protected_attributes + def test_has_one_create_with_admin_role_with_attr_protected_attributes best_friend = @person.best_friends.create(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_admin_scope_with_attr_accessible_attributes + def test_has_one_create_with_admin_role_with_attr_accessible_attributes best_friend = @person.best_friends.create(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end @@ -460,12 +488,12 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase assert_default_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes + def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes best_friend = @person.best_friends.create!(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes + def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes best_friend = @person.best_friends.create!(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index 7f0f007a70..a0cb5dbdc5 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -68,7 +68,7 @@ class MethodScopingTest < ActiveRecord::TestCase def test_scoped_find_all Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do - assert_equal [developers(:david)], Developer.find(:all) + assert_equal [developers(:david)], Developer.all end end @@ -235,23 +235,23 @@ class MethodScopingTest < ActiveRecord::TestCase def test_immutable_scope options = { :conditions => "name = 'David'" } Developer.send(:with_scope, :find => options) do - assert_equal %w(David), Developer.find(:all).map { |d| d.name } + assert_equal %w(David), Developer.all.map(&:name) options[:conditions] = "name != 'David'" - assert_equal %w(David), Developer.find(:all).map { |d| d.name } + assert_equal %w(David), Developer.all.map(&:name) end scope = { :find => { :conditions => "name = 'David'" }} Developer.send(:with_scope, scope) do - assert_equal %w(David), Developer.find(:all).map { |d| d.name } + assert_equal %w(David), Developer.all.map(&:name) scope[:find][:conditions] = "name != 'David'" - assert_equal %w(David), Developer.find(:all).map { |d| d.name } + assert_equal %w(David), Developer.all.map(&:name) end end def test_scoped_with_duck_typing scoping = Struct.new(:current_scope).new(:find => { :conditions => ["name = ?", 'David'] }) Developer.send(:with_scope, scoping) do - assert_equal %w(David), Developer.find(:all).map { |d| d.name } + assert_equal %w(David), Developer.all.map(&:name) end end @@ -432,7 +432,7 @@ class NestedScopingTest < ActiveRecord::TestCase def test_merged_scoped_find_combines_and_sanitizes_conditions Developer.send(:with_scope, :find => { :conditions => ["name = ?", 'David'] }) do Developer.send(:with_scope, :find => { :conditions => ['salary > ?', 9000] }) do - assert_equal %w(David), Developer.find(:all).map { |d| d.name } + assert_equal %w(David), Developer.all.map(&:name) end end end @@ -487,9 +487,9 @@ class NestedScopingTest < ActiveRecord::TestCase options2 = { :conditions => "name = 'David'" } Developer.send(:with_scope, :find => options1) do Developer.send(:with_exclusive_scope, :find => options2) do - assert_equal %w(David), Developer.find(:all).map { |d| d.name } + assert_equal %w(David), Developer.all.map(&:name) options1[:conditions] = options2[:conditions] = nil - assert_equal %w(David), Developer.find(:all).map { |d| d.name } + assert_equal %w(David), Developer.all.map(&:name) end end end @@ -499,9 +499,9 @@ class NestedScopingTest < ActiveRecord::TestCase options2 = { :conditions => "salary > 10000" } Developer.send(:with_scope, :find => options1) do Developer.send(:with_scope, :find => options2) do - assert_equal %w(Jamis), Developer.find(:all).map { |d| d.name } + assert_equal %w(Jamis), Developer.all.map(&:name) options1[:conditions] = options2[:conditions] = nil - assert_equal %w(Jamis), Developer.find(:all).map { |d| d.name } + assert_equal %w(Jamis), Developer.all.map(&:name) end end end diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index 8fd1fc2577..34188e4915 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -462,7 +462,7 @@ class NamedScopeTest < ActiveRecord::TestCase [:destroy_all, :reset, :delete_all].each do |method| before = post.comments.containing_the_letter_e post.association(:comments).send(method) - assert before.object_id != post.comments.containing_the_letter_e.object_id, "AssociationCollection##{method} should reset the named scopes cache" + assert before.object_id != post.comments.containing_the_letter_e.object_id, "CollectionAssociation##{method} should reset the named scopes cache" end end diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index b066575af8..57d1441128 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -336,6 +336,10 @@ class PersistencesTest < ActiveRecord::TestCase assert !Topic.find(1).approved? end + def test_update_attribute_does_not_choke_on_nil + assert Topic.find(1).update_attributes(nil) + end + def test_update_attribute_for_readonly_attribute minivan = Minivan.find('m1') assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_attribute(:color, 'black') } diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index b2e40c6b22..a61180cfaf 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -65,6 +65,17 @@ class QueryCacheTest < ActiveRecord::TestCase assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled' end + def test_cache_clear_after_close + mw = ActiveRecord::QueryCache.new lambda { |env| + Post.find(:first) + } + body = mw.call({}).last + + assert !ActiveRecord::Base.connection.query_cache.empty?, 'cache not empty' + body.close + assert ActiveRecord::Base.connection.query_cache.empty?, 'cache should be empty' + end + def test_find_queries assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) { Task.find(1); Task.find(1) } end diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index 864b3d4846..c215602567 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -462,4 +462,8 @@ class DefaultScopingTest < ActiveRecord::TestCase assert DeveloperCalledJamis.unscoped.poor.include?(developers(:david).becomes(DeveloperCalledJamis)) assert_equal 10, DeveloperCalledJamis.unscoped.poor.length end + + def test_default_scope_order_ignored_by_aggregations + assert_equal DeveloperOrderedBySalary.all.count, DeveloperOrderedBySalary.count + end end diff --git a/activerecord/test/cases/session_store/session_test.rb b/activerecord/test/cases/session_store/session_test.rb index cee5ddd003..669c0b7b4d 100644 --- a/activerecord/test/cases/session_store/session_test.rb +++ b/activerecord/test/cases/session_store/session_test.rb @@ -21,6 +21,12 @@ module ActiveRecord assert_equal 'sessions', Session.table_name end + def test_accessible_attributes + assert Session.accessible_attributes.include?(:session_id) + assert Session.accessible_attributes.include?(:data) + assert Session.accessible_attributes.include?(:marshaled_data) + end + def test_create_table! assert !Session.table_exists? Session.create_table! diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index ceb1452afd..22d4cac422 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -14,6 +14,32 @@ class TimestampTest < ActiveRecord::TestCase @previously_updated_at = @developer.updated_at end + def test_load_infinity_and_beyond + unless current_adapter?(:PostgreSQLAdapter) + return skip("only tested on postgresql") + end + + d = Developer.find_by_sql("select 'infinity'::timestamp as updated_at") + assert d.first.updated_at.infinite?, 'timestamp should be infinite' + + d = Developer.find_by_sql("select '-infinity'::timestamp as updated_at") + time = d.first.updated_at + assert time.infinite?, 'timestamp should be infinite' + assert_operator time, :<, 0 + end + + def test_save_infinity_and_beyond + unless current_adapter?(:PostgreSQLAdapter) + return skip("only tested on postgresql") + end + + d = Developer.create!(:name => 'aaron', :updated_at => 1.0 / 0.0) + assert_equal(1.0 / 0.0, d.updated_at) + + d = Developer.create!(:name => 'aaron', :updated_at => -1.0 / 0.0) + assert_equal(-1.0 / 0.0, d.updated_at) + end + def test_saving_a_changed_record_updates_its_timestamp @developer.name = "Jack Bauer" @developer.save! diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb index a6074b23e7..756c8a32eb 100644 --- a/activerecord/test/cases/xml_serialization_test.rb +++ b/activerecord/test/cases/xml_serialization_test.rb @@ -143,10 +143,7 @@ class NilXmlSerializationTest < ActiveRecord::TestCase end def test_should_serialize_yaml - assert %r{<preferences(.*)></preferences>}.match(@xml) - attributes = $1 - assert_match %r{type="yaml"}, attributes - assert_match %r{nil="true"}, attributes + assert_match %r{<preferences nil=\"true\"></preferences>}, @xml end end diff --git a/activerecord/test/fixtures/all/people.csv b/activerecord/test/fixtures/all/people.yml index e69de29bb2..e69de29bb2 100644 --- a/activerecord/test/fixtures/all/people.csv +++ b/activerecord/test/fixtures/all/people.yml diff --git a/activerecord/test/fixtures/mateys.yml b/activerecord/test/fixtures/mateys.yml index 9ecdd4ecd5..d3690955fc 100644 --- a/activerecord/test/fixtures/mateys.yml +++ b/activerecord/test/fixtures/mateys.yml @@ -1,4 +1,4 @@ blackbeard_to_redbeard: - pirate_id: <%= Fixtures.identify(:blackbeard) %> - target_id: <%= Fixtures.identify(:redbeard) %> + pirate_id: <%= ActiveRecord::Fixtures.identify(:blackbeard) %> + target_id: <%= ActiveRecord::Fixtures.identify(:redbeard) %> weight: 10 diff --git a/activerecord/test/fixtures/memberships.yml b/activerecord/test/fixtures/memberships.yml index 60eb641054..a5d52bd438 100644 --- a/activerecord/test/fixtures/memberships.yml +++ b/activerecord/test/fixtures/memberships.yml @@ -25,3 +25,10 @@ blarpy_winkup_crazy_club: member_id: 3 favourite: false type: CurrentMembership + +selected_membership_of_boring_club: + joined_on: <%= 3.weeks.ago.to_s(:db) %> + club: boring_club + member_id: 1 + favourite: false + type: SelectedMembership diff --git a/activerecord/test/fixtures/parrots_pirates.yml b/activerecord/test/fixtures/parrots_pirates.yml index 6b17a37d68..66472243c7 100644 --- a/activerecord/test/fixtures/parrots_pirates.yml +++ b/activerecord/test/fixtures/parrots_pirates.yml @@ -1,7 +1,7 @@ george_blackbeard: - parrot_id: <%= Fixtures.identify(:george) %> - pirate_id: <%= Fixtures.identify(:blackbeard) %> + parrot_id: <%= ActiveRecord::Fixtures.identify(:george) %> + pirate_id: <%= ActiveRecord::Fixtures.identify(:blackbeard) %> louis_blackbeard: - parrot_id: <%= Fixtures.identify(:louis) %> - pirate_id: <%= Fixtures.identify(:blackbeard) %> + parrot_id: <%= ActiveRecord::Fixtures.identify(:louis) %> + pirate_id: <%= ActiveRecord::Fixtures.identify(:blackbeard) %> diff --git a/activerecord/test/fixtures/string_key_objects.yml b/activerecord/test/fixtures/string_key_objects.yml new file mode 100644 index 0000000000..fa1299915b --- /dev/null +++ b/activerecord/test/fixtures/string_key_objects.yml @@ -0,0 +1,7 @@ +first: + id: record1 + name: first record + +second: + id: record2 + name: second record diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb index c68d008c26..643dcefed3 100644 --- a/activerecord/test/models/bulb.rb +++ b/activerecord/test/models/bulb.rb @@ -2,6 +2,8 @@ class Bulb < ActiveRecord::Base default_scope where(:name => 'defaulty') belongs_to :car + attr_protected :car_id, :frickinawesome + attr_reader :scope_after_initialize after_initialize :record_scope_after_initialize diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb index b036f0f5c9..76f20b1061 100644 --- a/activerecord/test/models/car.rb +++ b/activerecord/test/models/car.rb @@ -2,6 +2,11 @@ class Car < ActiveRecord::Base has_many :bulbs has_many :foo_bulbs, :class_name => "Bulb", :conditions => { :name => 'foo' } + has_many :frickinawesome_bulbs, :class_name => "Bulb", :conditions => { :frickinawesome => true } + + has_one :bulb + has_one :frickinawesome_bulb, :class_name => "Bulb", :conditions => { :frickinawesome => true } + has_many :tyres has_many :engines has_many :wheels, :as => :wheelable diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb index 991e0e051f..11a0f4ff63 100644 --- a/activerecord/test/models/member.rb +++ b/activerecord/test/models/member.rb @@ -1,8 +1,10 @@ class Member < ActiveRecord::Base has_one :current_membership + has_one :selected_membership has_one :membership has_many :fellow_members, :through => :club, :source => :members has_one :club, :through => :current_membership + has_one :selected_club, :through => :selected_membership, :source => :club has_one :favourite_club, :through => :membership, :conditions => ["memberships.favourite = ?", true], :source => :club has_one :hairy_club, :through => :membership, :conditions => {:clubs => {:name => "Moustache and Eyebrow Fancier Club"}}, :source => :club has_one :sponsor, :as => :sponsorable diff --git a/activerecord/test/models/membership.rb b/activerecord/test/models/membership.rb index 905f948c37..bcbb7e42c5 100644 --- a/activerecord/test/models/membership.rb +++ b/activerecord/test/models/membership.rb @@ -7,3 +7,9 @@ class CurrentMembership < Membership belongs_to :member belongs_to :club end + +class SelectedMembership < Membership + def self.default_scope + select("'1' as foo") + end +end diff --git a/activerecord/test/models/string_key_object.rb b/activerecord/test/models/string_key_object.rb new file mode 100644 index 0000000000..f8d4c6e0e4 --- /dev/null +++ b/activerecord/test/models/string_key_object.rb @@ -0,0 +1,3 @@ +class StringKeyObject < ActiveRecord::Base + set_primary_key :id +end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 9479242e4f..c8a98f121d 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -89,6 +89,7 @@ ActiveRecord::Schema.define do create_table :bulbs, :force => true do |t| t.integer :car_id t.string :name + t.boolean :frickinawesome end create_table "CamelCase", :force => true do |t| @@ -543,6 +544,12 @@ ActiveRecord::Schema.define do t.string :sponsorable_type end + create_table :string_key_objects, :id => false, :primary_key => :id, :force => true do |t| + t.string :id + t.string :name + t.integer :lock_version, :null => false, :default => 0 + end + create_table :students, :force => true do |t| t.string :name end diff --git a/activeresource/CHANGELOG b/activeresource/CHANGELOG index 386bafd7de..a4e79f3d77 100644 --- a/activeresource/CHANGELOG +++ b/activeresource/CHANGELOG @@ -2,14 +2,42 @@ * No changes -*Rails 3.0.2 (unreleased)* + +*Rails 3.0.7 (April 18, 2011)* + +*No changes. + + +*Rails 3.0.6 (April 5, 2011) + +* No changes. + + +*Rails 3.0.5 (February 26, 2011)* + +* No changes. + + +*Rails 3.0.4 (February 8, 2011)* + +* No changes. + + +*Rails 3.0.3 (November 16, 2010)* + +* No changes. + + +*Rails 3.0.2 (November 15, 2010)* * No changes + *Rails 3.0.1 (October 15, 2010)* * No Changes, just a version bump. + *Rails 3.0.0 (August 29, 2010)* * JSON: set Base.include_root_in_json = true to include a root value in the JSON: {"post": {"title": ...}}. Mirrors the Active Record option. [Santiago Pastorino] diff --git a/activeresource/lib/active_resource.rb b/activeresource/lib/active_resource.rb index 186865f811..0794a1a800 100644 --- a/activeresource/lib/active_resource.rb +++ b/activeresource/lib/active_resource.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2006 David Heinemeier Hansson +# Copyright (c) 2006-2011 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/activeresource/lib/active_resource/version.rb b/activeresource/lib/active_resource/version.rb index 82dcb5d575..f26e2312b9 100644 --- a/activeresource/lib/active_resource/version.rb +++ b/activeresource/lib/active_resource/version.rb @@ -3,7 +3,7 @@ module ActiveResource MAJOR = 3 MINOR = 1 TINY = 0 - PRE = "beta" + PRE = "beta1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/activeresource/test/cases/format_test.rb b/activeresource/test/cases/format_test.rb index fc1a7b8c6f..f8d33f99fa 100644 --- a/activeresource/test/cases/format_test.rb +++ b/activeresource/test/cases/format_test.rb @@ -86,7 +86,7 @@ class FormatTest < Test::Unit::TestCase def test_serialization_of_nested_resource address = { :street => '12345 Street' } - person = { :name=> 'Rus', :address => address} + person = { :name => 'Rus', :address => address} [:json, :xml].each do |format| encoded_person = ActiveResource::Formats[format].encode(person) diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 69e9cbfd42..23b0df1d5c 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *Rails 3.1.0 (unreleased)* +* New reporting method Kernel#quietly. [fxn] + * Add String#inquiry as a convenience method for turning a string into a StringInquirer object [DHH] * Add Object#in? to test if an object is included in another object [Prem Sichanugrist, Brian Morearty, John Reitano] @@ -19,14 +21,44 @@ advantage of the new ClassCache. * Added before_remove_const callback to ActiveSupport::Dependencies.remove_unloadable_constants! [Andrew White] -*Rails 3.0.2 (unreleased)* +* JSON decoding now uses the multi_json gem which also vendors a json engine called OkJson. The yaml backend has been removed in favor of OkJson as a default engine for 1.8.x, while the built in 1.9.x json implementation will be used by default. [Josh Kalderimis] + + +*Rails 3.0.7 (April 18, 2011)* + +* Hash.from_xml no longer loses attributes on tags containing only whitespace [André Arko] + + +*Rails 3.0.6 (April 5, 2011) + +* No changes. + + +*Rails 3.0.5 (February 26, 2011)* + +* No changes. + + +*Rails 3.0.4 (February 8, 2011)* + +* No changes. + + +*Rails 3.0.3 (November 16, 2010)* + +* No changes. + + +*Rails 3.0.2 (November 15, 2010)* * Added before_remove_const callback to ActiveSupport::Dependencies.remove_unloadable_constants! [Andrew White] + *Rails 3.0.1 (October 15, 2010)* * No Changes, just a version bump. + *Rails 3.0.0 (August 29, 2010)* * Implemented String#strip_heredoc. [fxn] diff --git a/activesupport/README.rdoc b/activesupport/README.rdoc index 13ca4b3bf1..8bb15e849a 100644 --- a/activesupport/README.rdoc +++ b/activesupport/README.rdoc @@ -30,4 +30,4 @@ API documentation is at Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here: -* https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets +* https://github.com/rails/rails/issues diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 6b87774978..a846f81c12 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2005 David Heinemeier Hansson +# Copyright (c) 2005-2011 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/activesupport/lib/active_support/buffered_logger.rb b/activesupport/lib/active_support/buffered_logger.rb index 88b50fc506..a14f008be5 100644 --- a/activesupport/lib/active_support/buffered_logger.rb +++ b/activesupport/lib/active_support/buffered_logger.rb @@ -49,10 +49,12 @@ module ActiveSupport @log = log elsif File.exist?(log) @log = open(log, (File::WRONLY | File::APPEND)) + @log.binmode @log.sync = true else FileUtils.mkdir_p(File.dirname(log)) @log = open(log, (File::WRONLY | File::APPEND | File::CREAT)) + @log.binmode @log.sync = true end end diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb index 8c56a21ef7..a94446acde 100644 --- a/activesupport/lib/active_support/configurable.rb +++ b/activesupport/lib/active_support/configurable.rb @@ -2,6 +2,7 @@ require 'active_support/concern' require 'active_support/ordered_options' require 'active_support/core_ext/kernel/singleton_class' require 'active_support/core_ext/module/delegation' +require 'active_support/core_ext/array/extract_options' module ActiveSupport # Configurable provides a <tt>config</tt> method to store and retrieve @@ -51,14 +52,16 @@ module ActiveSupport # user.allowed_access # => true # def config_accessor(*names) + options = names.extract_options! + names.each do |name| - code, line = <<-RUBY, __LINE__ + 1 - def #{name}; config.#{name}; end - def #{name}=(value); config.#{name} = value; end - RUBY + reader, line = "def #{name}; config.#{name}; end", __LINE__ + writer, line = "def #{name}=(value); config.#{name} = value; end", __LINE__ - singleton_class.class_eval code, __FILE__, line - class_eval code, __FILE__, line + singleton_class.class_eval reader, __FILE__, line + singleton_class.class_eval writer, __FILE__, line + class_eval reader, __FILE__, line unless options[:instance_reader] == false + class_eval writer, __FILE__, line unless options[:instance_writer] == false end end end diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index 724e076407..236055d77a 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -206,7 +206,7 @@ class Date # Returns a new ; DateTime objects will have time set to 0:00DateTime representing the start of the month (1st of the month; DateTime objects will have time set to 0:00) def beginning_of_month - self.acts_like?(:time) ? change(:day => 1,:hour => 0, :min => 0, :sec => 0) : change(:day => 1) + self.acts_like?(:time) ? change(:day => 1, :hour => 0) : change(:day => 1) end alias :at_beginning_of_month :beginning_of_month @@ -231,13 +231,13 @@ class Date # Returns a new Date/DateTime representing the start of the year (1st of january; DateTime objects will have time set to 0:00) def beginning_of_year - self.acts_like?(:time) ? change(:month => 1, :day => 1, :hour => 0, :min => 0, :sec => 0) : change(:month => 1, :day => 1) + self.acts_like?(:time) ? change(:month => 1, :day => 1, :hour => 0) : change(:month => 1, :day => 1) end alias :at_beginning_of_year :beginning_of_year # Returns a new Time representing the end of the year (31st of december; DateTime objects will have time set to 23:59:59) def end_of_year - self.acts_like?(:time) ? change(:month => 12,:day => 31,:hour => 23, :min => 59, :sec => 59) : change(:month => 12, :day => 31) + self.acts_like?(:time) ? change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59) : change(:month => 12, :day => 31) end alias :at_end_of_year :end_of_year diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb index 769ead9544..338104fd05 100644 --- a/activesupport/lib/active_support/core_ext/date/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -1,6 +1,7 @@ require 'date' require 'active_support/inflector/methods' require 'active_support/core_ext/date/zones' +require 'active_support/core_ext/module/remove_method' class Date DATE_FORMATS = { @@ -13,10 +14,10 @@ class Date } # Ruby 1.9 has Date#to_time which converts to localtime only. - remove_method :to_time if method_defined?(:to_time) + remove_possible_method :to_time # Ruby 1.9 has Date#xmlschema which converts to a string without the time component. - remove_method :xmlschema if method_defined?(:xmlschema) + remove_possible_method :xmlschema # Convert to a formatted string. See DATE_FORMATS for predefined formats. # diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 61a1d88b0e..102378a029 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -108,7 +108,8 @@ class Hash raise "can't typecast #{entries.inspect}" end end - elsif value['type'] == 'file' || value["__content__"].present? + elsif value['type'] == 'file' || + (value["__content__"] && (value.keys.size == 1 || value["__content__"].present?)) content = value["__content__"] if parser = ActiveSupport::XmlMini::PARSING[value["type"]] parser.arity == 1 ? parser.call(content) : parser.call(content, value) diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb index aad4b61e16..c2a6476604 100644 --- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb +++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb @@ -9,4 +9,16 @@ class Hash def with_indifferent_access ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default(self) end + + # Called when object is nested under an object that receives + # #with_indifferent_access. This method with be called on the current object + # by the enclosing object and is aliased to #with_indifferent_access by + # default. Subclasses of Hash may overwrite this method to return +self+ if + # converting to an +ActiveSupport::HashWithIndifferentAccess+ would not be + # desirable. + # + # b = {:b => 1} + # {:a => b}.with_indifferent_access["a"] # calls b.nested_under_indifferent_access + # + alias nested_under_indifferent_access with_indifferent_access end diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb index 37a827123a..c6920098a8 100644 --- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -62,7 +62,7 @@ module Kernel # Captures the given stream and returns it: # - # stream = capture(:stdout){ puts "Cool" } + # stream = capture(:stdout) { puts "Cool" } # stream # => "Cool\n" # def capture(stream) @@ -78,4 +78,16 @@ module Kernel result end alias :silence :capture + + # Silences both STDOUT and STDERR, even for subprocesses. + # + # quietly { system 'bundle install' } + # + def quietly + silence_stream(STDOUT) do + silence_stream(STDERR) do + yield + end + end + end end diff --git a/activesupport/lib/active_support/core_ext/module/deprecation.rb b/activesupport/lib/active_support/core_ext/module/deprecation.rb index 9c169a2598..5a5b4e3f80 100644 --- a/activesupport/lib/active_support/core_ext/module/deprecation.rb +++ b/activesupport/lib/active_support/core_ext/module/deprecation.rb @@ -1,5 +1,3 @@ -require 'active_support/deprecation' - class Module # Declare that a method has been deprecated. # deprecate :foo diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 7e134db118..dcac17536a 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -187,7 +187,7 @@ class Time # Returns a new Time representing the start of the day (0:00) def beginning_of_day #(self - seconds_since_midnight).change(:usec => 0) - change(:hour => 0, :min => 0, :sec => 0, :usec => 0) + change(:hour => 0) end alias :midnight :beginning_of_day alias :at_midnight :beginning_of_day @@ -201,7 +201,7 @@ class Time # Returns a new Time representing the start of the month (1st of the month, 0:00) def beginning_of_month #self - ((self.mday-1).days + self.seconds_since_midnight) - change(:day => 1,:hour => 0, :min => 0, :sec => 0, :usec => 0) + change(:day => 1, :hour => 0) end alias :at_beginning_of_month :beginning_of_month @@ -227,7 +227,7 @@ class Time # Returns a new Time representing the start of the year (1st of january, 0:00) def beginning_of_year - change(:month => 1, :day => 1, :hour => 0, :min => 0, :sec => 0, :usec => 0) + change(:month => 1, :day => 1, :hour => 0) end alias :at_beginning_of_year :beginning_of_year diff --git a/activesupport/lib/active_support/core_ext/time/marshal.rb b/activesupport/lib/active_support/core_ext/time/marshal.rb index 1a4d918ce7..457d3f5b62 100644 --- a/activesupport/lib/active_support/core_ext/time/marshal.rb +++ b/activesupport/lib/active_support/core_ext/time/marshal.rb @@ -37,6 +37,7 @@ if Time.local(2010).zone != Marshal.load(Marshal.dump(Time.local(2010))).zone time.instance_eval do if zone = defined?(@_zone) && remove_instance_variable('@_zone') ary = to_a + ary[0] += subsec if ary[0] == sec ary[-1] = zone utc? ? Time.utc(*ary) : Time.local(*ary) else diff --git a/activesupport/lib/active_support/descendants_tracker.rb b/activesupport/lib/active_support/descendants_tracker.rb index 4d1cfacc95..e2a8b4d4e3 100644 --- a/activesupport/lib/active_support/descendants_tracker.rb +++ b/activesupport/lib/active_support/descendants_tracker.rb @@ -1,5 +1,3 @@ -require 'active_support/dependencies' - module ActiveSupport # This module provides an internal implementation to track descendants # which is faster than iterating through ObjectSpace. @@ -18,12 +16,16 @@ module ActiveSupport end def self.clear - @@direct_descendants.each do |klass, descendants| - if ActiveSupport::Dependencies.autoloaded?(klass) - @@direct_descendants.delete(klass) - else - descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) } + if defined? ActiveSupport::Dependencies + @@direct_descendants.each do |klass, descendants| + if ActiveSupport::Dependencies.autoloaded?(klass) + @@direct_descendants.delete(klass) + else + descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) } + end end + else + @@direct_descendants.clear end end diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 79a0de7940..8ec4f6e09a 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -140,8 +140,8 @@ module ActiveSupport end def convert_value(value) - if value.class == Hash - self.class.new_from_hash_copying_default(value) + if value.is_a? Hash + value.nested_under_indifferent_access elsif value.is_a?(Array) value.dup.replace(value.map { |e| convert_value(e) }) else diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index de49750083..a2c4f7bfda 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -135,11 +135,13 @@ module ActiveSupport # ordinalize(2) # => "2nd" # ordinalize(1002) # => "1002nd" # ordinalize(1003) # => "1003rd" + # ordinalize(-11) # => "-11th" + # ordinalize(-1021) # => "-1021st" def ordinalize(number) - if (11..13).include?(number.to_i % 100) + if (11..13).include?(number.to_i.abs % 100) "#{number}th" else - case number.to_i % 10 + case number.to_i.abs % 10 when 1; "#{number}st" when 2; "#{number}nd" when 3; "#{number}rd" @@ -148,4 +150,4 @@ module ActiveSupport end end end -end
\ No newline at end of file +end diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index 82b8a7e148..d22fe14b33 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -2,6 +2,7 @@ require 'active_support/core_ext/object/to_json' require 'active_support/core_ext/module/delegation' require 'active_support/deprecation' require 'active_support/json/variable' +require 'active_support/ordered_hash' require 'bigdecimal' require 'active_support/core_ext/big_decimal/conversions' # for #to_s @@ -205,7 +206,9 @@ class Regexp end module Enumerable - def as_json(options = nil) to_a end #:nodoc: + def as_json(options = nil) #:nodoc: + to_a.as_json(options) + end end class Array diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb index 392e33edbc..3e54134c5c 100644 --- a/activesupport/lib/active_support/log_subscriber/test_helper.rb +++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb @@ -1,5 +1,6 @@ require 'active_support/log_subscriber' require 'active_support/buffered_logger' +require 'active_support/notifications' module ActiveSupport class LogSubscriber diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb index fbc40d1b69..762a64a881 100644 --- a/activesupport/lib/active_support/ordered_hash.rb +++ b/activesupport/lib/active_support/ordered_hash.rb @@ -43,6 +43,10 @@ module ActiveSupport end end + def nested_under_indifferent_access + self + end + # Hash is ordered in Ruby 1.9! if RUBY_VERSION < '1.9' diff --git a/activesupport/lib/active_support/secure_random.rb b/activesupport/lib/active_support/secure_random.rb index 368954907f..52f8c72b77 100644 --- a/activesupport/lib/active_support/secure_random.rb +++ b/activesupport/lib/active_support/secure_random.rb @@ -1,205 +1,6 @@ -begin - require 'securerandom' -rescue LoadError -end +require 'securerandom' module ActiveSupport - if defined?(::SecureRandom) - # Use Ruby's SecureRandom library if available. - SecureRandom = ::SecureRandom # :nodoc: - else - # = Secure random number generator interface. - # - # This library is an interface for secure random number generator which is - # suitable for generating session key in HTTP cookies, etc. - # - # It supports following secure random number generators. - # - # * openssl - # * /dev/urandom - # * Win32 - # - # *Note*: This module is based on the SecureRandom library from Ruby 1.9, - # revision 18786, August 23 2008. It's 100% interface-compatible with Ruby 1.9's - # SecureRandom library. - # - # == Example - # - # # random hexadecimal string. - # p SecureRandom.hex(10) # => "52750b30ffbc7de3b362" - # p SecureRandom.hex(10) # => "92b15d6c8dc4beb5f559" - # p SecureRandom.hex(11) # => "6aca1b5c58e4863e6b81b8" - # p SecureRandom.hex(12) # => "94b2fff3e7fd9b9c391a2306" - # p SecureRandom.hex(13) # => "39b290146bea6ce975c37cfc23" - # ... - # - # # random base64 string. - # p SecureRandom.base64(10) # => "EcmTPZwWRAozdA==" - # p SecureRandom.base64(10) # => "9b0nsevdwNuM/w==" - # p SecureRandom.base64(10) # => "KO1nIU+p9DKxGg==" - # p SecureRandom.base64(11) # => "l7XEiFja+8EKEtY=" - # p SecureRandom.base64(12) # => "7kJSM/MzBJI+75j8" - # p SecureRandom.base64(13) # => "vKLJ0tXBHqQOuIcSIg==" - # ... - # - # # random binary string. - # p SecureRandom.random_bytes(10) # => "\016\t{\370g\310pbr\301" - # p SecureRandom.random_bytes(10) # => "\323U\030TO\234\357\020\a\337" - # ... - # - module SecureRandom - - # Generates a random binary string. - # - # The argument n specifies the length of the result string. - # - # If n is not specified, 16 is assumed. - # It may be larger in future. - # - # If secure random number generator is not available, - # NotImplementedError is raised. - # - def self.random_bytes(n=nil) - n ||= 16 - - unless defined? OpenSSL - begin - require 'openssl' - rescue LoadError - end - end - - if defined? OpenSSL::Random - return OpenSSL::Random.random_bytes(n) - end - - if !defined?(@has_urandom) || @has_urandom - flags = File::RDONLY - flags |= File::NONBLOCK if defined? File::NONBLOCK - flags |= File::NOCTTY if defined? File::NOCTTY - flags |= File::NOFOLLOW if defined? File::NOFOLLOW - begin - File.open("/dev/urandom", flags) {|f| - unless f.stat.chardev? - raise Errno::ENOENT - end - @has_urandom = true - ret = f.readpartial(n) - if ret.length != n - raise NotImplementedError, "Unexpected partial read from random device" - end - return ret - } - rescue Errno::ENOENT - @has_urandom = false - end - end - - unless defined?(@has_win32) - begin - require 'Win32API' - - crypt_acquire_context = Win32API.new("advapi32", "CryptAcquireContext", 'PPPII', 'L') - @crypt_gen_random = Win32API.new("advapi32", "CryptGenRandom", 'LIP', 'L') - - hProvStr = " " * 4 - prov_rsa_full = 1 - crypt_verifycontext = 0xF0000000 - - if crypt_acquire_context.call(hProvStr, nil, nil, prov_rsa_full, crypt_verifycontext) == 0 - raise SystemCallError, "CryptAcquireContext failed: #{lastWin32ErrorMessage}" - end - @hProv, = hProvStr.unpack('L') - - @has_win32 = true - rescue LoadError - @has_win32 = false - end - end - if @has_win32 - bytes = " " * n - if @crypt_gen_random.call(@hProv, bytes.size, bytes) == 0 - raise SystemCallError, "CryptGenRandom failed: #{lastWin32ErrorMessage}" - end - return bytes - end - - raise NotImplementedError, "No random device" - end - - # Generates a random hex string. - # - # The argument n specifies the length of the random length. - # The length of the result string is twice of n. - # - # If n is not specified, 16 is assumed. - # It may be larger in future. - # - # If secure random number generator is not available, - # NotImplementedError is raised. - # - def self.hex(n=nil) - random_bytes(n).unpack("H*")[0] - end - - # Generates a random base64 string. - # - # The argument n specifies the length of the random length. - # The length of the result string is about 4/3 of n. - # - # If n is not specified, 16 is assumed. - # It may be larger in future. - # - # If secure random number generator is not available, - # NotImplementedError is raised. - # - def self.base64(n=nil) - [random_bytes(n)].pack("m*").delete("\n") - end - - # Generates a random number. - # - # If an positive integer is given as n, - # SecureRandom.random_number returns an integer: - # 0 <= SecureRandom.random_number(n) < n. - # - # If 0 is given or an argument is not given, - # SecureRandom.random_number returns an float: - # 0.0 <= SecureRandom.random_number() < 1.0. - # - def self.random_number(n=0) - if 0 < n - hex = n.to_s(16) - hex = '0' + hex if (hex.length & 1) == 1 - bin = [hex].pack("H*") - mask = bin[0] - mask |= mask >> 1 - mask |= mask >> 2 - mask |= mask >> 4 - begin - rnd = SecureRandom.random_bytes(bin.length) - rnd[0] = rnd[0] & mask - end until rnd < bin - rnd.unpack("H*")[0].hex - else - # assumption: Float::MANT_DIG <= 64 - i64 = SecureRandom.random_bytes(8).unpack("Q")[0] - Math.ldexp(i64 >> (64-Float::MANT_DIG), -Float::MANT_DIG) - end - end - - # Following code is based on David Garamond's GUID library for Ruby. - def self.lastWin32ErrorMessage # :nodoc: - get_last_error = Win32API.new("kernel32", "GetLastError", '', 'L') - format_message = Win32API.new("kernel32", "FormatMessageA", 'LPLLPLPPPPPPPP', 'L') - format_message_ignore_inserts = 0x00000200 - format_message_from_system = 0x00001000 - - code = get_last_error.call - msg = "\0" * 1024 - len = format_message.call(format_message_ignore_inserts + format_message_from_system, 0, code, 0, msg, 1024, nil, nil, nil, nil, nil, nil, nil, nil) - msg[0, len].tr("\r", '').chomp - end - end - end + # Use Ruby's SecureRandom library. + SecureRandom = ::SecureRandom # :nodoc: end diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb index 8c91a061fb..7cd9bfa947 100644 --- a/activesupport/lib/active_support/testing/performance.rb +++ b/activesupport/lib/active_support/testing/performance.rb @@ -3,12 +3,100 @@ begin require 'fileutils' require 'rails/version' + require 'active_support/concern' require 'active_support/core_ext/class/delegating_attributes' require 'active_support/core_ext/string/inflections' module ActiveSupport module Testing module Performance + extend ActiveSupport::Concern + + included do + superclass_delegating_accessor :profile_options + self.profile_options = DEFAULTS + + if defined?(MiniTest::Assertions) && TestCase < MiniTest::Assertions + include ForMiniTest + else + include ForClassicTestUnit + end + end + + module ForMiniTest + def run(runner) + @runner = runner + + run_warmup + if profile_options && metrics = profile_options[:metrics] + metrics.each do |metric_name| + if klass = Metrics[metric_name.to_sym] + run_profile(klass.new) + end + end + end + end + + def run_test(metric, mode) + result = '.' + begin + run_callbacks :setup + setup + metric.send(mode) { __send__ method_name } + rescue Exception => e + result = @runner.puke(self.class, method_name, e) + ensure + begin + teardown + run_callbacks :teardown, :enumerator => :reverse_each + rescue Exception => e + result = @runner.puke(self.class, method_name, e) + end + end + result + end + end + + module ForClassicTestUnit + def run(result) + return if method_name =~ /^default_test$/ + + yield(self.class::STARTED, name) + @_result = result + + run_warmup + if profile_options && metrics = profile_options[:metrics] + metrics.each do |metric_name| + if klass = Metrics[metric_name.to_sym] + run_profile(klass.new) + result.add_run + end + end + end + + yield(self.class::FINISHED, name) + end + + def run_test(metric, mode) + run_callbacks :setup + setup + metric.send(mode) { __send__ @method_name } + rescue ::Test::Unit::AssertionFailedError => e + add_failure(e.message, e.backtrace) + rescue StandardError, ScriptError => e + add_error(e) + ensure + begin + teardown + run_callbacks :teardown, :enumerator => :reverse_each + rescue ::Test::Unit::AssertionFailedError => e + add_failure(e.message, e.backtrace) + rescue StandardError, ScriptError => e + add_error(e) + end + end + end + DEFAULTS = if benchmark = ARGV.include?('--benchmark') # HAX for rake test { :benchmark => true, @@ -24,53 +112,10 @@ begin :output => 'tmp/performance' } end.freeze - def self.included(base) - base.superclass_delegating_accessor :profile_options - base.profile_options = DEFAULTS - end - def full_test_name "#{self.class.name}##{method_name}" end - def run(result) - return if method_name =~ /^default_test$/ - - yield(self.class::STARTED, name) - @_result = result - - run_warmup - if profile_options && metrics = profile_options[:metrics] - metrics.each do |metric_name| - if klass = Metrics[metric_name.to_sym] - run_profile(klass.new) - result.add_run - end - end - end - - yield(self.class::FINISHED, name) - end - - def run_test(metric, mode) - run_callbacks :setup - setup - metric.send(mode) { __send__ @method_name } - rescue ::Test::Unit::AssertionFailedError => e - add_failure(e.message, e.backtrace) - rescue StandardError, ScriptError => e - add_error(e) - ensure - begin - teardown - run_callbacks :teardown, :enumerator => :reverse_each - rescue ::Test::Unit::AssertionFailedError => e - add_failure(e.message, e.backtrace) - rescue StandardError, ScriptError => e - add_error(e) - end - end - protected def run_warmup GC.start diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb index 690fc7f0fc..c2cf39e391 100644 --- a/activesupport/lib/active_support/version.rb +++ b/activesupport/lib/active_support/version.rb @@ -3,7 +3,7 @@ module ActiveSupport MAJOR = 3 MINOR = 1 TINY = 0 - PRE = "beta" + PRE = "beta1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index cddfcddb57..6e12404ad4 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/module/delegation' +require 'active_support/core_ext/string/inflections' module ActiveSupport # = XmlMini @@ -138,7 +139,9 @@ module ActiveSupport protected def _dasherize(key) - key.gsub(/(?!^[_]*)_(?![_]*$)/, '-') + # $2 must be a non-greedy regex for this to work + left, middle, right = /\A(_*)(.*?)(_*)\Z/.match(key.strip)[1,3] + "#{left}#{middle.tr('_ ', '--')}#{right}" end # TODO: Add support for other encodings diff --git a/activesupport/test/buffered_logger_test.rb b/activesupport/test/buffered_logger_test.rb index 8d1b1c02c6..21049d685b 100644 --- a/activesupport/test/buffered_logger_test.rb +++ b/activesupport/test/buffered_logger_test.rb @@ -2,6 +2,7 @@ require 'abstract_unit' require 'multibyte_test_helpers' require 'stringio' require 'fileutils' +require 'tempfile' require 'active_support/buffered_logger' class BufferedLoggerTest < Test::Unit::TestCase @@ -16,6 +17,44 @@ class BufferedLoggerTest < Test::Unit::TestCase @logger = Logger.new(@output) end + def test_write_binary_data_to_existing_file + t = Tempfile.new ['development', 'log'] + t.binmode + t.write 'hi mom!' + t.close + + logger = Logger.new t.path + logger.level = Logger::DEBUG + + str = "\x80" + if str.respond_to?(:force_encoding) + str.force_encoding("ASCII-8BIT") + end + + logger.add Logger::DEBUG, str + logger.flush + ensure + logger.close + t.close true + end + + def test_write_binary_data_create_file + fname = File.join Dir.tmpdir, 'lol', 'rofl.log' + logger = Logger.new fname + logger.level = Logger::DEBUG + + str = "\x80" + if str.respond_to?(:force_encoding) + str.force_encoding("ASCII-8BIT") + end + + logger.add Logger::DEBUG, str + logger.flush + ensure + logger.close + File.unlink fname + end + def test_should_log_debugging_message_when_debugging @logger.level = Logger::DEBUG @logger.add(Logger::DEBUG, @message) diff --git a/activesupport/test/class_cache_test.rb b/activesupport/test/class_cache_test.rb index 8445af8d25..fc2d54515d 100644 --- a/activesupport/test/class_cache_test.rb +++ b/activesupport/test/class_cache_test.rb @@ -58,7 +58,7 @@ module ActiveSupport assert @cache.key?(ClassCacheTest.name) end - def test_new_rejects_strings + def test_new_rejects_strings_when_called_on_a_new_string assert_deprecated do @cache.new ClassCacheTest.name end diff --git a/activesupport/test/configurable_test.rb b/activesupport/test/configurable_test.rb index 2b28e61815..c6d8191298 100644 --- a/activesupport/test/configurable_test.rb +++ b/activesupport/test/configurable_test.rb @@ -5,6 +5,7 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase class Parent include ActiveSupport::Configurable config_accessor :foo + config_accessor :bar, :instance_reader => false, :instance_writer => false end class Child < Parent @@ -36,6 +37,12 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase assert_equal :bar, Parent.config.foo end + test "configuration accessors is not available on instance" do + instance = Parent.new + assert !instance.respond_to?(:bar) + assert !instance.respond_to?(:bar=) + end + test "configuration hash is available on instance" do instance = Parent.new assert_equal :bar, instance.config.foo diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index c0b529d9f8..c8312aa653 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -1,4 +1,5 @@ require 'abstract_unit' +require 'active_support/inflector' require 'active_support/time' require 'active_support/json' diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 012b956d7f..4557a10688 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -16,6 +16,12 @@ class HashExtTest < Test::Unit::TestCase class SubclassingHash < Hash end + class NonIndifferentHash < Hash + def nested_under_indifferent_access + self + end + end + def setup @strings = { 'a' => 1, 'b' => 2 } @symbols = { :a => 1, :b => 2 } @@ -109,9 +115,12 @@ class HashExtTest < Test::Unit::TestCase assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys! end - def test_hash_subclass - flash = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access - assert_kind_of SubclassingHash, flash["foo"] + def test_nested_under_indifferent_access + foo = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access + assert_kind_of ActiveSupport::HashWithIndifferentAccess, foo["foo"] + + foo = { "foo" => NonIndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access + assert_kind_of NonIndifferentHash, foo["foo"] end def test_indifferent_assorted @@ -897,7 +906,13 @@ class HashToXmlTest < Test::Unit::TestCase hash = Hash.from_xml(xml) assert_equal "bacon is the best", hash['blog']['name'] end - + + def test_empty_cdata_from_xml + xml = "<data><![CDATA[]]></data>" + + assert_equal "", Hash.from_xml(xml)["data"] + end + def test_xsd_like_types_from_xml bacon_xml = <<-EOT <bacon> @@ -940,7 +955,7 @@ class HashToXmlTest < Test::Unit::TestCase assert_equal expected_product_hash, Hash.from_xml(product_xml)["product"] end - + def test_should_use_default_value_for_unknown_key hash_wia = HashWithIndifferentAccess.new(3) assert_equal 3, hash_wia[:new_key] diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index f0c289a418..32675c884a 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -3,6 +3,7 @@ require 'date' require 'abstract_unit' require 'inflector_test_cases' +require 'active_support/inflector' require 'active_support/core_ext/string' require 'active_support/time' require 'active_support/core_ext/kernel/reporting' diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 53d497013a..44e02109b1 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -808,4 +808,11 @@ class TimeExtMarshalingTest < Test::Unit::TestCase assert_equal t.zone, unmarshaled.zone assert_equal t, unmarshaled end + + def test_marshalling_preserves_fractional_seconds + t = Time.parse('00:00:00.500') + unmarshaled = Marshal.load(Marshal.dump(t)) + assert_equal t.to_f, unmarshaled.to_f + assert_equal t, unmarshaled + end end diff --git a/activesupport/test/descendants_tracker_test.rb b/activesupport/test/descendants_tracker_test_cases.rb index 79fb893592..066ec8549b 100644 --- a/activesupport/test/descendants_tracker_test.rb +++ b/activesupport/test/descendants_tracker_test_cases.rb @@ -1,9 +1,4 @@ -require 'abstract_unit' -require 'test/unit' -require 'active_support' -require 'active_support/core_ext/hash/slice' - -class DescendantsTrackerTest < Test::Unit::TestCase +module DescendantsTrackerTestCases class Parent extend ActiveSupport::DescendantsTracker end @@ -34,7 +29,7 @@ class DescendantsTrackerTest < Test::Unit::TestCase assert_equal [], Child2.direct_descendants end - def test_clear_with_autoloaded_parent_children_and_granchildren + def test_clear mark_as_autoloaded(*ALL) do ActiveSupport::DescendantsTracker.clear ALL.each do |k| @@ -43,35 +38,22 @@ class DescendantsTrackerTest < Test::Unit::TestCase end end - def test_clear_with_autoloaded_children_and_granchildren - mark_as_autoloaded Child1, Grandchild1, Grandchild2 do - ActiveSupport::DescendantsTracker.clear - assert_equal [Child2], Parent.descendants - assert_equal [], Child2.descendants - end - end - - def test_clear_with_autoloaded_granchildren - mark_as_autoloaded Grandchild1, Grandchild2 do - ActiveSupport::DescendantsTracker.clear - assert_equal [Child1, Child2], Parent.descendants - assert_equal [], Child1.descendants - assert_equal [], Child2.descendants - end - end - protected def mark_as_autoloaded(*klasses) - old_autoloaded = ActiveSupport::Dependencies.autoloaded_constants.dup - ActiveSupport::Dependencies.autoloaded_constants = klasses.map(&:name) + # If ActiveSupport::Dependencies is not loaded, forget about autoloading. + # This allows using AS::DescendantsTracker without AS::Dependencies. + if defined? ActiveSupport::Dependencies + old_autoloaded = ActiveSupport::Dependencies.autoloaded_constants.dup + ActiveSupport::Dependencies.autoloaded_constants = klasses.map(&:name) + end old_descendants = ActiveSupport::DescendantsTracker.class_eval("@@direct_descendants").dup old_descendants.each { |k, v| old_descendants[k] = v.dup } yield ensure - ActiveSupport::Dependencies.autoloaded_constants = old_autoloaded + ActiveSupport::Dependencies.autoloaded_constants = old_autoloaded if defined? ActiveSupport::Dependencies ActiveSupport::DescendantsTracker.class_eval("@@direct_descendants").replace(old_descendants) end end
\ No newline at end of file diff --git a/activesupport/test/descendants_tracker_with_autoloading_test.rb b/activesupport/test/descendants_tracker_with_autoloading_test.rb new file mode 100644 index 0000000000..ae18a56f44 --- /dev/null +++ b/activesupport/test/descendants_tracker_with_autoloading_test.rb @@ -0,0 +1,35 @@ +require 'abstract_unit' +require 'test/unit' +require 'active_support/descendants_tracker' +require 'active_support/dependencies' +require 'descendants_tracker_test_cases' + +class DescendantsTrackerWithAutoloadingTest < Test::Unit::TestCase + include DescendantsTrackerTestCases + + def test_clear_with_autoloaded_parent_children_and_granchildren + mark_as_autoloaded(*ALL) do + ActiveSupport::DescendantsTracker.clear + ALL.each do |k| + assert ActiveSupport::DescendantsTracker.descendants(k).empty? + end + end + end + + def test_clear_with_autoloaded_children_and_granchildren + mark_as_autoloaded Child1, Grandchild1, Grandchild2 do + ActiveSupport::DescendantsTracker.clear + assert_equal [Child2], Parent.descendants + assert_equal [], Child2.descendants + end + end + + def test_clear_with_autoloaded_granchildren + mark_as_autoloaded Grandchild1, Grandchild2 do + ActiveSupport::DescendantsTracker.clear + assert_equal [Child1, Child2], Parent.descendants + assert_equal [], Child1.descendants + assert_equal [], Child2.descendants + end + end +end
\ No newline at end of file diff --git a/activesupport/test/descendants_tracker_without_autoloading_test.rb b/activesupport/test/descendants_tracker_without_autoloading_test.rb new file mode 100644 index 0000000000..1f0c32dc3f --- /dev/null +++ b/activesupport/test/descendants_tracker_without_autoloading_test.rb @@ -0,0 +1,8 @@ +require 'abstract_unit' +require 'test/unit' +require 'active_support/descendants_tracker' +require 'descendants_tracker_test_cases' + +class DescendantsTrackerWithoutAutoloadingTest < Test::Unit::TestCase + include DescendantsTrackerTestCases +end
\ No newline at end of file diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index 2b144e5931..ec9d92794c 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -215,6 +215,36 @@ module InflectorTestCases } OrdinalNumbers = { + "-1" => "-1st", + "-2" => "-2nd", + "-3" => "-3rd", + "-4" => "-4th", + "-5" => "-5th", + "-6" => "-6th", + "-7" => "-7th", + "-8" => "-8th", + "-9" => "-9th", + "-10" => "-10th", + "-11" => "-11th", + "-12" => "-12th", + "-13" => "-13th", + "-14" => "-14th", + "-20" => "-20th", + "-21" => "-21st", + "-22" => "-22nd", + "-23" => "-23rd", + "-24" => "-24th", + "-100" => "-100th", + "-101" => "-101st", + "-102" => "-102nd", + "-103" => "-103rd", + "-104" => "-104th", + "-110" => "-110th", + "-111" => "-111th", + "-112" => "-112th", + "-113" => "-113th", + "-1000" => "-1000th", + "-1001" => "-1001st", "0" => "0th", "1" => "1st", "2" => "2nd", diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index d5fcbf15b7..8cf1a54a99 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -215,6 +215,30 @@ class TestJSONEncoding < Test::Unit::TestCase assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) end + def test_enumerable_should_pass_encoding_options_to_children_in_as_json + people = [ + { :name => 'John', :address => { :city => 'London', :country => 'UK' }}, + { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }} + ] + json = people.each.as_json :only => [:address, :city] + expected = [ + { 'address' => { 'city' => 'London' }}, + { 'address' => { 'city' => 'Paris' }} + ] + + assert_equal(expected, json) + end + + def test_enumerable_should_pass_encoding_options_to_children_in_to_json + people = [ + { :name => 'John', :address => { :city => 'London', :country => 'UK' }}, + { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }} + ] + json = people.each.to_json :only => [:address, :city] + + assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) + end + def test_struct_encoding Struct.new('UserNameAndEmail', :name, :email) Struct.new('UserNameAndDate', :name, :date) @@ -259,12 +283,3 @@ class TestJSONEncoding < Test::Unit::TestCase old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') end end - -class JsonOptionsTests < Test::Unit::TestCase - def test_enumerable_should_passthrough_options_to_elements - value, options = Object.new, Object.new - def value.as_json(options) options end - def options.encode_json(encoder) self end - assert_equal options, ActiveSupport::JSON.encode(value, options) - end -end diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb index 6ebbfdf334..bfff10fff2 100644 --- a/activesupport/test/multibyte_chars_test.rb +++ b/activesupport/test/multibyte_chars_test.rb @@ -1,6 +1,7 @@ # encoding: utf-8 require 'abstract_unit' require 'multibyte_test_helpers' +require 'active_support/core_ext/string/multibyte' class String def __method_for_multibyte_testing_with_integer_result; 1; end diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb index 50168fa78f..f3dcd7b068 100644 --- a/activesupport/test/ordered_hash_test.rb +++ b/activesupport/test/ordered_hash_test.rb @@ -1,6 +1,7 @@ require 'abstract_unit' require 'active_support/json' require 'active_support/core_ext/object/to_json' +require 'active_support/core_ext/hash/indifferent_access' class OrderedHashTest < Test::Unit::TestCase def setup @@ -243,6 +244,11 @@ class OrderedHashTest < Test::Unit::TestCase assert_equal @other_ordered_hash.keys, @ordered_hash.keys end + def test_nested_under_indifferent_access + flash = {:a => ActiveSupport::OrderedHash[:b, 1, :c, 2]}.with_indifferent_access + assert_kind_of ActiveSupport::OrderedHash, flash[:a] + end + def test_each_after_yaml_serialization values = [] @deserialized_ordered_hash = YAML.load(YAML.dump(@ordered_hash)) diff --git a/activesupport/test/test_xml_mini.rb b/activesupport/test/xml_mini_test.rb index 6dbcd1f40b..e2b90ae16e 100644 --- a/activesupport/test/test_xml_mini.rb +++ b/activesupport/test/xml_mini_test.rb @@ -16,14 +16,6 @@ module XmlMiniTest assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :dasherize => false) end - def test_rename_key_camelizes_with_camelize_false - assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :camelize => false) - end - - def test_rename_key_camelizes_with_camelize_nil - assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :camelize => nil) - end - def test_rename_key_camelizes_with_camelize_true assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => true) end @@ -56,7 +48,7 @@ module XmlMiniTest assert_equal "__id", ActiveSupport::XmlMini.rename_key("__id") end - def test_rename_key_does_not_dasherize_multiple_leading_underscores + def test_rename_key_does_not_dasherize_multiple_trailing_underscores assert_equal "id__", ActiveSupport::XmlMini.rename_key("id__") end end @@ -95,6 +87,16 @@ module XmlMiniTest @xml.to_tag(:b, "Howdy", @options) assert_xml "<b>Howdy</b>" end + + test "#to_tag should dasherize the space when passed a string with spaces as a key" do + @xml.to_tag("New York", 33, @options) + assert_xml "<New---York type=\"integer\">33</New---York>" + end + + test "#to_tag should dasherize the space when passed a symbol with spaces as a key" do + @xml.to_tag(:"New York", 33, @options) + assert_xml "<New---York type=\"integer\">33</New---York>" + end # TODO: test the remaining functions hidden in #to_tag. end end diff --git a/railties/CHANGELOG b/railties/CHANGELOG index 0def5bcf6c..c465b08594 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,5 +1,19 @@ *Rails 3.1.0 (unreleased)* +* Application and plugin generation run bundle install unless --skip-gemfile or --skip-bundle. [fxn] + +* Fixed database tasks for jdbc* adapters #jruby + + [Rashmi Yadav] + +* Template generation for jdbcpostgresql #jruby + + [Vishnu Atrai] + +* Template generation for jdbcmysql and jdbcsqlite3 #jruby + + [Arun Agrawal] + * The -j option of the application generator accepts an arbitrary string. If passed "foo", the gem "foo-rails" is added to the Gemfile, and the application JavaScript manifest requires "foo" and "foo_ujs". As of this writing "prototype-rails" and "jquery-rails" @@ -16,7 +30,7 @@ by the prototype-rails gem. [fxn] * jQuery is the new default JavaScript library. [fxn] -* Changed scaffold and app generator to create Ruby 1.9 style hash when running on Ruby 1.9 [Prem Sichanugrist] +* Changed scaffold, application, and mailer generator to create Ruby 1.9 style hash when running on Ruby 1.9 [Prem Sichanugrist] So instead of creating something like: @@ -62,10 +76,42 @@ by the prototype-rails gem. [fxn] * Include all helpers from plugins and shared engines in application [Piotr Sarnacki] + +*Rails 3.0.7 (April 18, 2011)* + +*No changes. + + +*Rails 3.0.6 (April 5, 2011) + +* No changes. + + +*Rails 3.0.5 (February 26, 2011)* + +* No changes. + + +*Rails 3.0.4 (February 8, 2011)* + +* No changes. + + +*Rails 3.0.3 (November 16, 2010)* + +* No changes. + + +*Rails 3.0.2 (November 15, 2010)* + +* No changes. + + *Rails 3.0.1 (October 15, 2010)* * No Changes, just a version bump. + *Rails 3.0.0 (August 29, 2010)* * Application generation: --skip-testunit and --skip-activerecord become --skip-test-unit and --skip-active-record respectively. [fxn] diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 8f98c71aa0..66869b4eeb 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -417,6 +417,14 @@ silence_stream(STDOUT) do end </ruby> +The +quietly+ method addresses the common use case where you want to silence STDOUT and STDERR, even in subprocesses: + +<ruby> +quietly { system 'bundle install' } +</ruby> + +For example, the railties test suite uses that one in a few places to prevent command messages from being echoed intermixed with the progress status. + Silencing exceptions is also possible with +suppress+. This method receives an arbitrary number of exception classes. If an exception is raised during the execution of the block and is +kind_of?+ any of the arguments, +suppress+ captures it and returns silently. Otherwise the exception is reraised: <ruby> diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile index 8c408ec06b..e0ccc7a6e6 100644 --- a/railties/guides/source/security.textile +++ b/railties/guides/source/security.textile @@ -372,7 +372,7 @@ def signup end </ruby> -Mass-assignment saves you much work, because you don't have to set each value individually. Simply pass a hash to the new() method, or assign attributes=(attributes) a hash value, to set the model's attributes to the values in the hash. The problem is that it is often used in conjunction with the parameters (params) hash available in the controller, which may be manipulated by an attacker. He may do so by changing the URL like this: +Mass-assignment saves you much work, because you don't have to set each value individually. Simply pass a hash to the +new+ method, or +assign_attributes=+ a hash value, to set the model's attributes to the values in the hash. The problem is that it is often used in conjunction with the parameters (params) hash available in the controller, which may be manipulated by an attacker. He may do so by changing the URL like this: <pre> "name":http://www.example.com/user/signup?user[name]=ow3ned&user[admin]=1 @@ -386,7 +386,7 @@ params[:user] # => {:name => “ow3ned”, :admin => true} So if you create a new user using mass-assignment, it may be too easy to become an administrator. -Note that this vulnerability is not restricted to database columns. Any setter method, unless explicitly protected, is accessible via the <tt>attributes=</tt> method. In fact, this vulnerability is extended even further with the introduction of nested mass assignment (and nested object forms) in Rails 2.3. The +accepts_nested_attributes_for+ declaration provides us the ability to extend mass assignment to model associations (+has_many+, +has_one+, +has_and_belongs_to_many+). For example: +Note that this vulnerability is not restricted to database columns. Any setter method, unless explicitly protected, is accessible via the <tt>attributes=</tt> method. In fact, this vulnerability is extended even further with the introduction of nested mass assignment (and nested object forms) in Rails 2.3+. The +accepts_nested_attributes_for+ declaration provides us the ability to extend mass assignment to model associations (+has_many+, +has_one+, +has_and_belongs_to_many+). For example: <ruby> class Person < ActiveRecord::Base @@ -410,7 +410,7 @@ To avoid this, Rails provides two class methods in your Active Record class to c attr_protected :admin </ruby> -+attr_protected+ also optionally takes a scope option using :as which allows you to define multiple mass-assignment groupings. If no scope is defined then attributes will be added to the default group. ++attr_protected+ also optionally takes a role option using :as which allows you to define multiple mass-assignment groupings. If no role is defined then attributes will be added to the :default role. <ruby> attr_protected :last_login, :as => :admin @@ -433,7 +433,7 @@ params[:user] # => {:name => "ow3ned", :admin => true} @user.admin # => true </ruby> -When assigning attributes in Active Record using +attributes=+, or +update_attributes+ the :default scope will be used. To assign attributes using different scopes you should use +assign_attributes+ which accepts an optional :as options parameter. If no :as option is provided then the :default scope will be used. You can also bypass mass-assignment security by using the +:without_protection+ option. Here is an example: +When assigning attributes in Active Record using +attributes=+ the :default role will be used. To assign attributes using different roles you should use +assign_attributes+ which accepts an optional :as options parameter. If no :as option is provided then the :default role will be used. You can also bypass mass-assignment security by using the +:without_protection+ option. Here is an example: <ruby> @user = User.new @@ -451,7 +451,7 @@ When assigning attributes in Active Record using +attributes=+, or +update_attri @user.is_admin # => true </ruby> -In a similar way, +new+, +create+ and <tt>create!</tt> methods respect mass-assignment security and accepts either +:as+ or +:without_protection+ options. For example: +In a similar way, +new+, +create+, <tt>create!</tt>, +update_attributes+, and +update_attributes!+ methods all respect mass-assignment security and accept either +:as+ or +:without_protection+ options. For example: <ruby> @user = User.new({ :name => 'Sebastian', :is_admin => true }, :as => :admin) diff --git a/railties/guides/source/testing.textile b/railties/guides/source/testing.textile index 01badebd41..7a93c3a1e6 100644 --- a/railties/guides/source/testing.textile +++ b/railties/guides/source/testing.textile @@ -54,7 +54,7 @@ For good tests, you'll need to give some thought to setting up test data. In Rai h5. What are Fixtures? -_Fixtures_ is a fancy word for sample data. Fixtures allow you to populate your testing database with predefined data before your tests run. Fixtures are database independent and assume one of two formats: *YAML* or *CSV*. In this guide, we will use *YAML*, which is the preferred format. +_Fixtures_ is a fancy word for sample data. Fixtures allow you to populate your testing database with predefined data before your tests run. Fixtures are database independent and assume a single format: *YAML*. You'll find fixtures under your +test/fixtures+ directory. When you run +rails generate model+ to create a new model, fixture stubs will be automatically created and placed in this directory. @@ -81,7 +81,7 @@ Each fixture is given a name followed by an indented list of colon-separated key h5. ERB'in It Up -ERB allows you to embed ruby code within templates. Both the YAML and CSV fixture formats are pre-processed with ERB when you load fixtures. This allows you to use Ruby to help you generate some sample data. +ERB allows you to embed ruby code within templates. YAML fixture format is pre-processed with ERB when you load fixtures. This allows you to use Ruby to help you generate some sample data. <erb> <% earth_size = 20 %> diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index dd01bbab1d..1e4d25f18c 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -157,7 +157,8 @@ module Rails middleware.use ::Rack::Lock unless config.allow_concurrency middleware.use ::Rack::Runtime - middleware.use ::Rails::Rack::Logger + middleware.use ::Rack::MethodOverride + middleware.use ::Rails::Rack::Logger # must come after Rack::MethodOverride to properly log overridden methods middleware.use ::ActionDispatch::ShowExceptions, config.consider_all_requests_local middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header @@ -171,7 +172,6 @@ module Rails end middleware.use ::ActionDispatch::ParamsParser - middleware.use ::Rack::MethodOverride middleware.use ::ActionDispatch::Head middleware.use ::Rack::ConditionalGet middleware.use ::Rack::ETag, "no-cache" @@ -199,4 +199,4 @@ module Rails require "rails/console/helpers" end end -end
\ No newline at end of file +end diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb index 9c9d85eed6..9f21d273e6 100644 --- a/railties/lib/rails/application/bootstrap.rb +++ b/railties/lib/rails/application/bootstrap.rb @@ -1,4 +1,5 @@ require "active_support/notifications" +require "active_support/dependencies" require "active_support/descendants_tracker" module Rails @@ -9,7 +10,6 @@ module Rails initializer :load_environment_hook do end initializer :load_active_support do - require 'active_support/dependencies' require "active_support/all" unless config.active_support.bare end @@ -37,6 +37,7 @@ module Rails ) logger end + at_exit { Rails.logger.flush if Rails.logger.respond_to?(:flush) } end # Initialize cache early in the stack so railties can make use of it. diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 29b9c27a13..3b74de690a 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -34,7 +34,7 @@ module Rails @assets = ActiveSupport::OrderedOptions.new @assets.enabled = false @assets.paths = [] - @assets.precompile = [ /\w+\.(?!js|css)$/, "application.js", "application.css" ] + @assets.precompile = [ /\w+\.(?!js|css).+/, "application.js", "application.css" ] @assets.prefix = "/assets" @assets.js_compressor = nil diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb index 4182757346..4a082aedb8 100644 --- a/railties/lib/rails/commands.rb +++ b/railties/lib/rails/commands.rb @@ -6,7 +6,8 @@ aliases = { "g" => "generate", "c" => "console", "s" => "server", - "db" => "dbconsole" + "db" => "dbconsole", + "r" => "runner" } command = ARGV.shift diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb index f0d6ea1687..b0ba76217a 100644 --- a/railties/lib/rails/commands/dbconsole.rb +++ b/railties/lib/rails/commands/dbconsole.rb @@ -23,7 +23,7 @@ module Rails include_password = false options = {} OptionParser.new do |opt| - opt.banner = "Usage: dbconsole [options] [environment]" + opt.banner = "Usage: dbconsole [environment] [options]" opt.on("-p", "--include-password", "Automatically provide the password from database.yml") do |v| include_password = true end diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb index 1a91d477ec..f8b00e7249 100644 --- a/railties/lib/rails/commands/runner.rb +++ b/railties/lib/rails/commands/runner.rb @@ -25,7 +25,7 @@ ARGV.clone.options do |opts| opts.separator "-------------------------------------------------------------" opts.separator "#!/usr/bin/env #{File.expand_path($0)} runner" opts.separator "" - opts.separator "Product.find(:all).each { |p| p.price *= 2 ; p.save! }" + opts.separator "Product.all.each { |p| p.price *= 2 ; p.save! }" opts.separator "-------------------------------------------------------------" end @@ -39,18 +39,12 @@ ENV["RAILS_ENV"] = options[:environment] require APP_PATH Rails.application.require_environment! -begin - if code_or_file.nil? - $stderr.puts "Run '#{$0} -h' for help." - exit 1 - elsif File.exist?(code_or_file) - $0 = code_or_file - eval(File.read(code_or_file), nil, code_or_file) - else - eval(code_or_file) - end -ensure - if defined? Rails - Rails.logger.flush if Rails.logger.respond_to?(:flush) - end +if code_or_file.nil? + $stderr.puts "Run '#{$0} -h' for help." + exit 1 +elsif File.exist?(code_or_file) + $0 = code_or_file + eval(File.read(code_or_file), nil, code_or_file) +else + eval(code_or_file) end diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb index e447209242..505a4ca2bd 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server.rb @@ -55,8 +55,9 @@ module Rails end def start + url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}" puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}" - puts "=> Rails #{Rails.version} application starting in #{Rails.env} on http://#{options[:Host]}:#{options[:Port]}" + puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}" puts "=> Call with -d to detach" unless options[:daemonize] trap(:INT) { exit } puts "=> Ctrl-C to shutdown server" unless options[:daemonize] diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 2015a944f0..6a125685d0 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -523,6 +523,7 @@ module Rails initializer :append_assets_path do |app| app.config.assets.paths.unshift *paths["vendor/assets"].existent + app.config.assets.paths.unshift *paths["lib/assets"].existent app.config.assets.paths.unshift *paths["app/assets"].existent end diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index 241db4b0a9..f424492bb4 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -47,6 +47,7 @@ module Rails paths.add "app/mailers", :eager_load => true paths.add "app/views" paths.add "lib", :load_path => true + paths.add "lib/assets", :glob => "*" paths.add "lib/tasks", :glob => "**/*.rake" paths.add "config" paths.add "config/environments", :glob => "#{Rails.env}.rb" diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 9be395e989..85c67af19a 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -52,13 +52,13 @@ module Rails :integration_tool => nil, :javascripts => true, :javascript_engine => nil, - :orm => nil, + :orm => false, :performance_tool => nil, :resource_controller => :controller, :scaffold_controller => :scaffold_controller, :stylesheets => true, :stylesheet_engine => nil, - :test_framework => nil, + :test_framework => false, :template_engine => :erb }, diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 998957f313..a5743762e5 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -28,6 +28,9 @@ module Rails class_option :skip_gemfile, :type => :boolean, :default => false, :desc => "Don't create a Gemfile" + class_option :skip_bundle, :type => :boolean, :default => false, + :desc => "Don't run bundle install" + class_option :skip_git, :type => :boolean, :aliases => "-G", :default => false, :desc => "Skip Git ignores and keeps" @@ -117,15 +120,15 @@ module Rails end def database_gemfile_entry - entry = options[:skip_active_record] ? "" : "gem '#{gem_for_database}'" - if options[:database] == 'mysql' - if options.dev? || options.edge? - entry += ", :git => 'git://github.com/brianmario/mysql2.git'" - else - entry += "\n# gem 'mysql2', :git => 'git://github.com/brianmario/mysql2.git'" - end - end - entry + "\n" + options[:skip_active_record] ? "" : "gem '#{gem_for_database}'\n" + end + + def include_all_railties? + !options[:skip_active_record] && !options[:skip_test_unit] + end + + def comment_if(value) + options[value] ? '#' : '' end def rails_gemfile_entry @@ -184,13 +187,18 @@ module Rails "gem '#{options[:javascript]}-rails'" unless options[:skip_javascript] end - def bundle_if_dev_or_edge - bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle') - run "#{bundle_command} install" if dev_or_edge? + def bundle_command(command) + require 'bundler' + require 'bundler/cli' + + say_status :run, "bundle #{command}" + Bundler::CLI.new.send(command) + rescue + say_status :failure, "bundler raised an exception, are you offline?", :red end - def dev_or_edge? - options.dev? || options.edge? + def run_bundle + bundle_command('install') unless options[:skip_gemfile] || options[:skip_bundle] end def empty_directory_with_gitkeep(destination, config = {}) diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index 36bc9e055c..cf317eb21f 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -1,3 +1,4 @@ +require 'active_support/core_ext/module/introspection' require 'rails/generators/base' require 'rails/generators/generated_attribute' diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 6562667782..5f9fb9685c 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -9,11 +9,20 @@ module Rails @options = generator.options end - private + private + %w(template copy_file directory empty_directory inside + empty_directory_with_gitkeep create_file chmod shebang).each do |method| + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{method}(*args, &block) + @generator.send(:#{method}, *args, &block) + end + RUBY + end - def method_missing(meth, *args, &block) - @generator.send(meth, *args, &block) - end + # TODO: Remove once this is fully in place + def method_missing(meth, *args, &block) + @generator.send(meth, *args, &block) + end end # The application builder allows you to override elements of the application @@ -215,7 +224,7 @@ module Rails build(:leftovers) end - public_task :apply_rails_template, :bundle_if_dev_or_edge + public_task :apply_rails_template, :run_bundle protected diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index b046edd5b7..20bd9db624 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -4,11 +4,12 @@ source 'http://rubygems.org' <%= database_gemfile_entry -%> +<%= "gem 'jruby-openssl'\n" if defined?(JRUBY_VERSION) && JRUBY_VERSION < "1.6" -%> # Asset template engines <%= "gem 'json'\n" if RUBY_VERSION < "1.9.2" -%> gem 'sass' gem 'coffee-script' -gem 'uglifier' +# gem 'uglifier' <%= gem_for_javascript %> @@ -21,4 +22,4 @@ gem 'uglifier' # To use debugger # <%= gem_for_ruby_debugger %> -<%= gem_for_turn -%>
\ No newline at end of file +<%= gem_for_turn -%> diff --git a/railties/lib/rails/generators/rails/app/templates/README b/railties/lib/rails/generators/rails/app/templates/README index 9f0f1d0e38..7c36f2356e 100644 --- a/railties/lib/rails/generators/rails/app/templates/README +++ b/railties/lib/rails/generators/rails/app/templates/README @@ -156,6 +156,10 @@ PostgreSQL and SQLite 3. The default directory structure of a generated Ruby on Rails application: |-- app + | |-- assets + | |-- images + | |-- javascripts + | `-- stylesheets | |-- controllers | |-- helpers | |-- mailers @@ -172,9 +176,6 @@ The default directory structure of a generated Ruby on Rails application: | `-- tasks |-- log |-- public - | |-- images - | |-- javascripts - | `-- stylesheets |-- script |-- test | |-- fixtures @@ -188,11 +189,16 @@ The default directory structure of a generated Ruby on Rails application: | |-- sessions | `-- sockets `-- vendor + |-- assets + `-- stylesheets `-- plugins app Holds all the code that's specific to this particular application. +app/assets + Contains subdirectories for images, stylesheets, and JavaScript files. + app/controllers Holds controllers that should be named like weblogs_controller.rb for automated URL mapping. All controllers should descend from @@ -237,8 +243,7 @@ lib the load path. public - The directory available for the web server. Contains subdirectories for - images, stylesheets, and javascripts. Also contains the dispatchers and the + The directory available for the web server. Also contains the dispatchers and the default HTML files. This should be set as the DOCUMENT_ROOT of your web server. diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt index 612c614f2e..19294b3478 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt @@ -1,5 +1,8 @@ -// FIXME: Tell people that this is a manifest file, real code should go into discrete files -// FIXME: Tell people how Sprockets and CoffeeScript works +// This is a manifest file that'll be compiled into including all the files listed below. +// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically +// be included in the compiled file accessible from http://example.com/assets/application.js +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// the compiled file. // <% unless options[:skip_javascript] -%> //= require <%= options[:javascript] %> diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css index f4b082ccc0..fc25b5723f 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css @@ -1,5 +1,7 @@ /* - * FIXME: Introduce SCSS & Sprockets + * This is a manifest file that'll automatically include all the stylesheets available in this directory + * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at + * the top of the compiled file, but it's generally better to create a new file per style scope. *= require_self *= require_tree . */
\ No newline at end of file diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index 3723addf2b..8ff80c6fd3 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -1,14 +1,14 @@ require File.expand_path('../boot', __FILE__) -<% unless options[:skip_active_record] -%> +<% if include_all_railties? -%> require 'rails/all' <% else -%> # Pick the frameworks you want: -# require "active_record/railtie" +<%= comment_if :skip_active_record %> require "active_record/railtie" require "action_controller/railtie" require "action_mailer/railtie" require "active_resource/railtie" -require "rails/test_unit/railtie" +<%= comment_if :skip_test_unit %> require "rails/test_unit/railtie" <% end -%> # If you have a Gemfile, require the gems listed there, including any gems @@ -50,21 +50,12 @@ module <%= app_const_base %> # config.action_view.javascript_expansions[:defaults] = %w(prototype prototype_ujs) <% end -%> -<% if options[:skip_test_unit] -%> - config.generators.test_framework = false -<% end -%> - # Configure the default encoding used in templates for Ruby 1.9. config.encoding = "utf-8" # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password] -<% unless options[:skip_active_record] -%> - # Enable IdentityMap for Active Record, to disable set to false or remove the line below. - config.active_record.identity_map = true -<% end -%> - # Enable the asset pipeline config.assets.enabled = true 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 41b2374eda..066aa54862 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 @@ -22,4 +22,3 @@ # Only use best-standards-support built into browsers config.action_dispatch.best_standards_support = :builtin end - diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt index 32ffbee7a1..e56195da80 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt @@ -4,7 +4,7 @@ # which will be enabled by default in the upcoming version of Ruby on Rails. # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. -ActionController::Base.wrap_parameters :format => [:json] +ActionController::Base.wrap_parameters <%= key_value :format, "[:json]" %> # Disable root element in JSON by default. if defined?(ActiveRecord) diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore index f0fa30c536..923b697662 100644 --- a/railties/lib/rails/generators/rails/app/templates/gitignore +++ b/railties/lib/rails/generators/rails/app/templates/gitignore @@ -2,3 +2,4 @@ db/*.sqlite3 log/*.log tmp/ +.sass-cache/ diff --git a/railties/lib/rails/generators/rails/assets/assets_generator.rb b/railties/lib/rails/generators/rails/assets/assets_generator.rb index 80beb7abfe..2d52da77eb 100644 --- a/railties/lib/rails/generators/rails/assets/assets_generator.rb +++ b/railties/lib/rails/generators/rails/assets/assets_generator.rb @@ -1,11 +1,11 @@ module Rails module Generators class AssetsGenerator < NamedBase - class_option :javascripts, :type => :boolean, :desc => "Generate javascripts" - class_option :stylesheets, :type => :boolean, :desc => "Generate stylesheets" + class_option :javascripts, :type => :boolean, :desc => "Generate JavaScripts" + class_option :stylesheets, :type => :boolean, :desc => "Generate Stylesheets" - class_option :javascript_engine, :desc => "Engine for javascripts" - class_option :stylesheet_engine, :desc => "Engine for stylesheets" + class_option :javascript_engine, :desc => "Engine for JavaScripts" + class_option :stylesheet_engine, :desc => "Engine for Stylesheets" def create_javascript_files return unless options.javascripts? diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb index 6201595308..939c0cd727 100644 --- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb +++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb @@ -209,7 +209,7 @@ task :default => :test build(:leftovers) end - public_task :apply_rails_template, :bundle_if_dev_or_edge + public_task :apply_rails_template, :run_bundle protected diff --git a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb index 6eef0dbe5b..aa9b45c5a5 100644 --- a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb @@ -6,8 +6,8 @@ module Rails remove_hook_for :resource_controller remove_class_option :actions - class_option :stylesheets, :type => :boolean, :desc => "Generate stylesheets" - class_option :stylesheet_engine, :desc => "Engine for stylesheets" + class_option :stylesheets, :type => :boolean, :desc => "Generate Stylesheets" + class_option :stylesheet_engine, :desc => "Engine for Stylesheets" hook_for :scaffold_controller, :required => true diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index 5222b7bf50..77a5c4dc6c 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -1,6 +1,6 @@ namespace :rails do - desc "Update both configs and public/javascripts from Rails (or use just update:javascripts or update:configs)" - task :update => [ "update:configs", "update:javascripts", "update:scripts", "update:application_controller" ] + desc "Update configs and some other initially generated files (or use just update:configs, update:scripts, or update:application_controller)" + task :update => [ "update:configs", "update:scripts", "update:application_controller" ] desc "Applies the template supplied by LOCATION=/path/to/template" task :template do @@ -58,11 +58,6 @@ namespace :rails do invoke_from_app_generator :create_config_files end - # desc "Update Prototype javascripts from your current rails install" - task :javascripts do - invoke_from_app_generator :create_javascript_files - end - # desc "Adds new scripts to the application script/ directory" task :scripts do invoke_from_app_generator :create_script_files diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake index 02e22361e0..a0c953967c 100644 --- a/railties/lib/rails/tasks/routes.rake +++ b/railties/lib/rails/tasks/routes.rake @@ -16,7 +16,8 @@ task :routes => :environment do {:name => route.name.to_s, :verb => route.verb.to_s, :path => route.path, :reqs => reqs} end - routes.reject! { |r| r[:path] =~ %r{/rails/info/properties} } # Skip the route if it's internal info route + # Skip the route if it's internal info route + routes.reject! { |r| r[:path] =~ %r{/rails/info/properties|^/assets} } name_width = routes.map{ |r| r[:name].length }.max verb_width = routes.map{ |r| r[:verb].length }.max diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb index b076881c21..fc6c0a0204 100644 --- a/railties/lib/rails/version.rb +++ b/railties/lib/rails/version.rb @@ -3,7 +3,7 @@ module Rails MAJOR = 3 MINOR = 1 TINY = 0 - PRE = "beta" + PRE = "beta1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/railties/railties.gemspec b/railties/railties.gemspec index cd0646b8ed..4404838670 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |s| s.rdoc_options << '--exclude' << '.' s.add_dependency('rake', '>= 0.8.7') - s.add_dependency('thor', '~> 0.14.4') + s.add_dependency('thor', '~> 0.14.6') s.add_dependency('rack-ssl', '~> 1.3.2') s.add_dependency('activesupport', version) s.add_dependency('actionpack', version) diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb new file mode 100644 index 0000000000..b03dc3132b --- /dev/null +++ b/railties/test/application/assets_test.rb @@ -0,0 +1,27 @@ +require 'isolation/abstract_unit' +require 'rack/test' + +module ApplicationTests + class RoutingTest < Test::Unit::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + def setup + build_app + boot_rails + end + + test "assets routes have higher priority" do + app_file "app/assets/javascripts/demo.js.erb", "<%= :alert %>();" + + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do + match '*path', :to => lambda { |env| [200, { "Content-Type" => "text/html" }, "Not an asset"] } + end + RUBY + + get "/assets/demo.js" + assert_match "alert()", last_response.body + end + end +end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index b1f7076776..43876c0a72 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -12,7 +12,6 @@ end class ::MyOtherMailObserver < ::MyMailObserver; end - module ApplicationTests class ConfigurationTest < Test::Unit::TestCase include ActiveSupport::Testing::Isolation @@ -226,8 +225,6 @@ module ApplicationTests make_basic_app class ::OmgController < ActionController::Base - protect_from_forgery - def index render :inline => "<%= csrf_meta_tags %>" end @@ -237,6 +234,21 @@ module ApplicationTests assert last_response.body =~ /csrf\-param/ end + test "request forgery token param can be changed" do + make_basic_app do + app.config.action_controller.request_forgery_protection_token = '_xsrf_token_here' + end + + class ::OmgController < ActionController::Base + def index + render :inline => "<%= csrf_meta_tags %>" + end + end + + get "/" + assert last_response.body =~ /_xsrf_token_here/ + end + test "config.action_controller.perform_caching = true" do make_basic_app do |app| app.config.action_controller.perform_caching = true @@ -437,10 +449,35 @@ module ApplicationTests app_file 'config/initializers/wrap_parameters.rb', <<-RUBY ActionController::Base.wrap_parameters :format => [:json] RUBY + + app_file 'app/models/post.rb', <<-RUBY + class Post + def self.column_names + %w(title) + end + end + RUBY + + app_file 'app/controllers/posts_controller.rb', <<-RUBY + class PostsController < ApplicationController + def index + render :text => params[:post].inspect + end + end + RUBY + + add_to_config <<-RUBY + routes.append do + resources :posts + end + RUBY + require "#{app_path}/config/environment" - require 'action_controller/base' + require "rack/test" + extend Rack::Test::Methods - assert_equal [:json], ActionController::Base._wrapper_options[:format] + post "/posts.json", '{ "title": "foo", "name": "bar" }', "CONTENT_TYPE" => "application/json" + assert_equal '{"title"=>"foo"}', last_response.body end test "config.action_dispatch.ignore_accept_header" do diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index 19311a7fa0..196d121c14 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -166,7 +166,7 @@ module ApplicationTests require "#{app_path}/config/environment" - expects = [ActiveRecord::IdentityMap::Middleware, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActiveRecord::SessionStore] + expects = [ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActiveRecord::SessionStore] middleware = Rails.application.config.middleware.map { |m| m.klass } assert_equal expects, middleware & expects end diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index 01e6c49d9c..8bfde00af5 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -23,20 +23,19 @@ module ApplicationTests "Rack::Lock", "ActiveSupport::Cache::Strategy::LocalCache", "Rack::Runtime", - "Rails::Rack::Logger", + "Rack::MethodOverride", + "Rails::Rack::Logger", # must come after Rack::MethodOverride to properly log overridden methods "ActionDispatch::ShowExceptions", "ActionDispatch::RemoteIp", "Rack::Sendfile", "ActionDispatch::Reloader", "ActionDispatch::Callbacks", - "ActiveRecord::IdentityMap::Middleware", "ActiveRecord::ConnectionAdapters::ConnectionManagement", "ActiveRecord::QueryCache", "ActionDispatch::Cookies", "ActionDispatch::Session::CookieStore", "ActionDispatch::Flash", "ActionDispatch::ParamsParser", - "Rack::MethodOverride", "ActionDispatch::Head", "Rack::ConditionalGet", "Rack::ETag", @@ -121,6 +120,7 @@ module ApplicationTests end test "identity map is inserted" do + add_to_config "config.active_record.identity_map = true" boot! assert middleware.include?("ActiveRecord::IdentityMap::Middleware") end diff --git a/railties/test/application/rack/logger_test.rb b/railties/test/application/rack/logger_test.rb new file mode 100644 index 0000000000..a29244357c --- /dev/null +++ b/railties/test/application/rack/logger_test.rb @@ -0,0 +1,40 @@ +require "isolation/abstract_unit" +require "active_support/log_subscriber/test_helper" +require "rack/test" + +module ApplicationTests + module RackTests + class LoggerTest < Test::Unit::TestCase + include ActiveSupport::LogSubscriber::TestHelper + include Rack::Test::Methods + + def setup + build_app + require "#{app_path}/config/environment" + super + end + + def logs + @logs ||= @logger.logged(:info) + end + + test "logger logs proper HTTP verb and path" do + get "/blah" + wait + assert_match /^Started GET "\/blah"/, logs[0] + end + + test "logger logs HTTP verb override" do + post "/", {:_method => 'put'} + wait + assert_match /^Started PUT "\/"/, logs[0] + end + + test "logger logs HEAD requests" do + post "/", {:_method => 'head'} + wait + assert_match /^Started HEAD "\/"/, logs[0] + end + end + end +end diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index 59e5ef4dee..d77c2d14ab 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -64,6 +64,28 @@ module ApplicationTests assert_match 'cart GET /cart(.:format)', Dir.chdir(app_path){ `rake routes` } end + def test_rake_routes_shows_custom_assets + app_file "config/routes.rb", <<-RUBY + AppTemplate::Application.routes.draw do + get '/custom/assets', :to => 'custom_assets#show' + end + RUBY + assert_match 'custom_assets GET /custom/assets(.:format)', Dir.chdir(app_path){ `rake routes` } + end + + def test_logger_is_flushed_when_exiting_production_rake_tasks + add_to_config <<-RUBY + rake_tasks do + task :log_something => :environment do + Rails.logger.error("Sample log message") + end + end + RUBY + + output = Dir.chdir(app_path){ `rake log_something RAILS_ENV=production && cat log/production.log` } + assert_match "Sample log message", output + end + def test_model_and_migration_generator_with_change_syntax Dir.chdir(app_path) do `rails generate model user username:string password:string` diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb index 62589c998d..e3a7f8a63c 100644 --- a/railties/test/application/routing_test.rb +++ b/railties/test/application/routing_test.rb @@ -1,14 +1,14 @@ require 'isolation/abstract_unit' +require 'rack/test' module ApplicationTests class RoutingTest < Test::Unit::TestCase include ActiveSupport::Testing::Isolation + include Rack::Test::Methods def setup build_app boot_rails - require 'rack/test' - extend Rack::Test::Methods end test "rails/info/properties in development" do diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb index 1fbbb40132..f96319f472 100644 --- a/railties/test/application/test_test.rb +++ b/railties/test/application/test_test.rb @@ -65,6 +65,31 @@ module ApplicationTests run_test 'integration/posts_test.rb' end + test "performance test" do + controller 'posts', <<-RUBY + class PostsController < ActionController::Base + end + RUBY + + app_file 'app/views/posts/index.html.erb', <<-HTML + Posts#index + HTML + + app_file 'test/performance/posts_test.rb', <<-RUBY + require 'test_helper' + require 'rails/performance_test_help' + + class PostsTest < ActionDispatch::PerformanceTest + def test_index + get '/posts' + assert_response :success + end + end + RUBY + + run_test 'performance/posts_test.rb' + end + private def run_test(name) result = ruby '-Itest', "#{app_path}/test/#{name}" diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 81263a6ce9..9e1d47cd2f 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -39,6 +39,8 @@ DEFAULT_APP_FILES = %w( class AppGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper arguments [destination_root] + + # brings setup, teardown, and some tests include SharedGeneratorTests def default_files @@ -64,8 +66,7 @@ class AppGeneratorTest < Rails::Generators::TestCase end def test_application_new_exits_with_non_zero_code_on_invalid_application_name - # TODO: Suppress the output of this (it's because of a Thor::Error) - `rails new test` + quietly { system 'rails new test' } assert_equal false, $?.success? end @@ -95,7 +96,7 @@ class AppGeneratorTest < Rails::Generators::TestCase generator = Rails::Generators::AppGenerator.new ["rails"], { :with_dispatchers => true }, :destination_root => app_moved_root, :shell => @shell generator.send(:app_const) - silence(:stdout){ generator.send(:create_config_files) } + quietly { generator.send(:create_config_files) } assert_file "myapp_moved/config/environment.rb", /Myapp::Application\.initialize!/ assert_file "myapp_moved/config/initializers/session_store.rb", /_myapp_session/ end @@ -110,7 +111,7 @@ class AppGeneratorTest < Rails::Generators::TestCase generator = Rails::Generators::AppGenerator.new ["rails"], { :with_dispatchers => true }, :destination_root => app_root, :shell => @shell generator.send(:app_const) - silence(:stdout){ generator.send(:create_config_files) } + quietly { generator.send(:create_config_files) } assert_file "myapp/config/initializers/session_store.rb", /_myapp_session/ end @@ -135,6 +136,7 @@ class AppGeneratorTest < Rails::Generators::TestCase run_generator([destination_root, "-d", "jdbcmysql"]) assert_file "config/database.yml", /jdbcmysql/ assert_file "Gemfile", /^gem\s+["']activerecord-jdbcmysql-adapter["']$/ + assert_file "Gemfile", /^gem\s+["']jruby-openssl["']$/ if defined?(JRUBY_VERSION) && JRUBY_VERSION < "1.6" end def test_config_jdbcsqlite3_database @@ -226,9 +228,13 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_test_unit_is_removed_from_frameworks_if_skip_test_unit_is_given run_generator [destination_root, "--skip-test-unit"] - assert_file "config/application.rb" do |file| - assert_match /config.generators.test_framework = false/, file - end + assert_file "config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/ + end + + def test_no_active_record_or_test_unit_if_skips_given + run_generator [destination_root, "--skip-test-unit", "--skip-active-record"] + assert_file "config/application.rb", /#\s+require\s+["']rails\/test_unit\/railtie["']/ + assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ end def test_new_hash_style @@ -252,7 +258,7 @@ class AppGeneratorTest < Rails::Generators::TestCase protected def action(*args, &block) - silence(:stdout){ generator.send(*args, &block) } + silence(:stdout) { generator.send(*args, &block) } end end @@ -278,6 +284,6 @@ protected end def action(*args, &block) - silence(:stdout){ generator.send(*args, &block) } + silence(:stdout) { generator.send(*args, &block) } end end diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb index f4fdc46328..bf1cfe5305 100644 --- a/railties/test/generators/mailer_generator_test.rb +++ b/railties/test/generators/mailer_generator_test.rb @@ -10,7 +10,11 @@ class MailerGeneratorTest < Rails::Generators::TestCase run_generator assert_file "app/mailers/notifier.rb" do |mailer| assert_match /class Notifier < ActionMailer::Base/, mailer - assert_match /default :from => "from@example.com"/, mailer + if RUBY_VERSION < "1.9" + assert_match /default :from => "from@example.com"/, mailer + else + assert_match /default from: "from@example.com"/, mailer + end end end @@ -73,15 +77,33 @@ class MailerGeneratorTest < Rails::Generators::TestCase assert_file "app/mailers/notifier.rb" do |mailer| assert_instance_method :foo, mailer do |foo| - assert_match /mail :to => "to@example.org"/, foo + if RUBY_VERSION < "1.9" + assert_match /mail :to => "to@example.org"/, foo + else + assert_match /mail to: "to@example.org"/, foo + end assert_match /@greeting = "Hi"/, foo end assert_instance_method :bar, mailer do |bar| - assert_match /mail :to => "to@example.org"/, bar + if RUBY_VERSION < "1.9" + assert_match /mail :to => "to@example.org"/, bar + else + assert_match /mail to: "to@example.org"/, bar + end assert_match /@greeting = "Hi"/, bar end end + end + def test_force_old_style_hash + run_generator ["notifier", "foo", "--old-style-hash"] + assert_file "app/mailers/notifier.rb" do |mailer| + assert_match /default :from => "from@example.com"/, mailer + + assert_instance_method :foo, mailer do |foo| + assert_match /mail :to => "to@example.org"/, foo + end + end end end diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb index eb56e8d1a4..38f024f061 100644 --- a/railties/test/generators/namespaced_generators_test.rb +++ b/railties/test/generators/namespaced_generators_test.rb @@ -163,7 +163,11 @@ class NamespacedMailerGeneratorTest < NamespacedGeneratorTestCase assert_file "app/mailers/test_app/notifier.rb" do |mailer| assert_match /module TestApp/, mailer assert_match /class Notifier < ActionMailer::Base/, mailer - assert_match /default :from => "from@example.com"/, mailer + if RUBY_VERSION < "1.9" + assert_match /default :from => "from@example.com"/, mailer + else + assert_match /default from: "from@example.com"/, mailer + end end end diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index 33c8d83f9c..2af728e766 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -22,6 +22,8 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper destination File.join(Rails.root, "tmp/bukkits") arguments [destination_root] + + # brings setup, teardown, and some tests include SharedGeneratorTests def default_files @@ -117,17 +119,17 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_match(/It works from file!/, run_generator([destination_root, "-m", "lib/template.rb"])) end - def test_ensure_that_tests_works + def test_ensure_that_tests_work run_generator FileUtils.cd destination_root - `bundle install` + quietly { system 'bundle install' } assert_match(/1 tests, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test`) end def test_ensure_that_tests_works_in_full_mode run_generator [destination_root, "--full", "--skip_active_record"] FileUtils.cd destination_root - `bundle install` + quietly { system 'bundle install' } assert_match(/1 tests, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test`) end diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb index c9c5d2fad2..be9aef8a41 100644 --- a/railties/test/generators/shared_generator_tests.rb +++ b/railties/test/generators/shared_generator_tests.rb @@ -1,9 +1,11 @@ +# +# Tests, setup, and teardown common to the application and plugin generator suites. +# module SharedGeneratorTests def setup Rails.application = TestApp::Application super Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) - @bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle') Kernel::silence_warnings do Thor::Base.shell.send(:attr_accessor, :always_force) @@ -21,7 +23,12 @@ module SharedGeneratorTests def test_skeleton_is_created run_generator - default_files.each{ |path| assert_file path } + default_files.each { |path| assert_file path } + end + + def test_generation_runs_bundle_install + generator([destination_root]).expects(:bundle_command).with('install').once + quietly { generator.invoke_all } end def test_plugin_new_generate_pretend @@ -81,20 +88,7 @@ module SharedGeneratorTests template.instance_eval "def read; self; end" # Make the string respond to read generator([destination_root], :template => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template) - assert_match(/It works!/, silence(:stdout){ generator.invoke_all }) - end - - def test_dev_option - generator([destination_root], :dev => true).expects(:run).with("#{@bundle_command} install") - silence(:stdout){ generator.invoke_all } - rails_path = File.expand_path('../../..', Rails.root) - assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:path\s+=>\s+["']#{Regexp.escape(rails_path)}["']$/ - end - - def test_edge_option - generator([destination_root], :edge => true).expects(:run).with("#{@bundle_command} install") - silence(:stdout){ generator.invoke_all } - assert_file 'Gemfile', %r{^gem\s+["']rails["'],\s+:git\s+=>\s+["']#{Regexp.escape("git://github.com/rails/rails.git")}["']$} + assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) end def test_template_raises_an_error_with_invalid_path @@ -109,7 +103,7 @@ module SharedGeneratorTests template.instance_eval "def read; self; end" # Make the string respond to read generator([destination_root], :template => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template) - assert_match(/It works!/, silence(:stdout){ generator.invoke_all }) + assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) end def test_template_is_executed_when_supplied_an_https_path @@ -118,21 +112,36 @@ module SharedGeneratorTests template.instance_eval "def read; self; end" # Make the string respond to read generator([destination_root], :template => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template) - assert_match(/It works!/, silence(:stdout){ generator.invoke_all }) + assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) end def test_dev_option - generator([destination_root], :dev => true).expects(:run).with("#{@bundle_command} install") - silence(:stdout){ generator.invoke_all } + generator([destination_root], :dev => true).expects(:bundle_command).with('install').once + quietly { generator.invoke_all } rails_path = File.expand_path('../../..', Rails.root) assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:path\s+=>\s+["']#{Regexp.escape(rails_path)}["']$/ end def test_edge_option - generator([destination_root], :edge => true).expects(:run).with("#{@bundle_command} install") - silence(:stdout){ generator.invoke_all } + 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")}["']$} end + + def test_skip_gemfile + generator([destination_root], :skip_gemfile => true).expects(:bundle_command).never + quietly { generator.invoke_all } + assert_no_file 'Gemfile' + end + + def test_skip_bundle + generator([destination_root], :skip_bundle => true).expects(:bundle_command).never + quietly { generator.invoke_all } + + # skip_bundle is only about running bundle install, ensure the Gemfile is still + # generated. + assert_file 'Gemfile' + end end module SharedCustomGeneratorTests @@ -140,7 +149,6 @@ module SharedCustomGeneratorTests Rails.application = TestApp::Application super Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) - @bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle') end def teardown @@ -188,7 +196,7 @@ module SharedCustomGeneratorTests template.instance_eval "def read; self; end" # Make the string respond to read generator([destination_root], :builder => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template) - capture(:stdout) { generator.invoke_all } + quietly { generator.invoke_all } default_files.each{ |path| assert_no_file(path) } end diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 0c588ba773..b5b21f9ebe 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -93,6 +93,34 @@ module RailtiesTest assert_equal "HELLO WORLD", last_response.body end + test "pass the value of the segment" do + controller "foo", <<-RUBY + class FooController < ActionController::Base + def index + render :text => params[:username] + end + end + RUBY + + @plugin.write "config/routes.rb", <<-RUBY + Bukkits::Engine.routes.draw do + root :to => "foo#index" + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + mount(Bukkits::Engine => "/:username") + end + RUBY + + boot_rails + + get("/arunagw") + assert_equal "arunagw", last_response.body + + end + test "it provides routes as default endpoint" do @plugin.write "lib/bukkits.rb", <<-RUBY class Bukkits diff --git a/railties/test/railties/shared_tests.rb b/railties/test/railties/shared_tests.rb index e975950b85..e5debf29ae 100644 --- a/railties/test/railties/shared_tests.rb +++ b/railties/test/railties/shared_tests.rb @@ -15,11 +15,10 @@ module RailtiesTest boot_rails require 'rack/test' - require 'coffee_script' extend Rack::Test::Methods get "/assets/engine.js" - assert_match "alert();", last_response.body + assert_match "alert()", last_response.body end def test_copying_migrations diff --git a/version.rb b/version.rb index b076881c21..fc6c0a0204 100644 --- a/version.rb +++ b/version.rb @@ -3,7 +3,7 @@ module Rails MAJOR = 3 MINOR = 1 TINY = 0 - PRE = "beta" + PRE = "beta1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end |