diff options
123 files changed, 1528 insertions, 684 deletions
@@ -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/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/actionpack/CHANGELOG b/actionpack/CHANGELOG index ba01c4749f..428a5d0e1a 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,9 @@ *Rails 3.1.0 (unreleased)* +* 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 +116,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] diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 4aa2c894f9..fdd894fe63 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('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..881af74147 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 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/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/url.rb b/actionpack/lib/action_dispatch/http/url.rb index ac0fd9607d..d9c07d6ca3 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -165,7 +165,7 @@ module ActionDispatch # such as 2 to catch <tt>["www"]</tt> instead of <tt>"www.rubyonrails"</tt> # in "www.rubyonrails.co.uk". def subdomain(tld_length = @@tld_length) - subdomains(tld_length) + subdomains(tld_length).join(".") end end end diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 9c9ccc62f5..8ebf870b95 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -59,7 +59,7 @@ module ActionDispatch end def set_session(env, sid, session_data, options) - persistent_session_id!(session_data, sid) + session_data.merge("session_id" => sid) end def set_cookie(env, session_id, cookie) 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_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/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 9277359d5c..d2ddaafcb3 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+. @@ -230,6 +242,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 @@ -257,6 +272,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) @@ -306,6 +324,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') @@ -387,6 +408,9 @@ module ActionView # # separated by ':' and includes an input for seconds # select_time(my_time, :time_separator => ':', :include_seconds => true) # + # # 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 :prompt=>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 @@ -468,6 +492,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 @@ -601,6 +628,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 @@ -692,7 +728,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 @@ -810,7 +846,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 = [] @@ -818,7 +854,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/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/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb index 9c10decd60..0b4b0638b2 100644 --- a/actionpack/lib/sprockets/railtie.rb +++ b/actionpack/lib/sprockets/railtie.rb @@ -39,7 +39,7 @@ module Sprockets end end - app.routes.append do + app.routes.prepend do mount app.assets => assets.prefix 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/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index fb2faf7a4e..25b1b4f745 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -137,30 +137,39 @@ class RequestTest < ActiveSupport::TestCase test "subdomains" do request = stub_request 'HTTP_HOST' => "www.rubyonrails.org" assert_equal %w( www ), request.subdomains + assert_equal "www", request.subdomain request = stub_request 'HTTP_HOST' => "www.rubyonrails.co.uk" assert_equal %w( www ), request.subdomains(2) + assert_equal "www", request.subdomain(2) request = stub_request 'HTTP_HOST' => "dev.www.rubyonrails.co.uk" assert_equal %w( dev www ), request.subdomains(2) + assert_equal "dev.www", request.subdomain(2) request = stub_request 'HTTP_HOST' => "dev.www.rubyonrails.co.uk", :tld_length => 2 assert_equal %w( dev www ), request.subdomains + assert_equal "dev.www", request.subdomain request = stub_request 'HTTP_HOST' => "foobar.foobar.com" assert_equal %w( foobar ), request.subdomains + assert_equal "foobar", request.subdomain request = stub_request 'HTTP_HOST' => "192.168.1.200" assert_equal [], request.subdomains + assert_equal "", request.subdomain request = stub_request 'HTTP_HOST' => "foo.192.168.1.200" assert_equal [], request.subdomains + assert_equal "", request.subdomain request = stub_request 'HTTP_HOST' => "192.168.1.200.com" assert_equal %w( 192 168 1 ), request.subdomains + assert_equal "192.168.1", request.subdomain request = stub_request 'HTTP_HOST' => nil assert_equal [], request.subdomains + assert_equal "", request.subdomain end test "standard_port" do @@ -349,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/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index 27f55fd7ab..b0efbcef4a 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -50,6 +50,11 @@ class CookieStoreTest < ActionDispatch::IntegrationTest get_session_id end + def renew_session_id + request.session_options[:renew] = true + head :ok + end + def rescue_action(e) raise end end @@ -102,6 +107,17 @@ class CookieStoreTest < ActionDispatch::IntegrationTest end end + def test_properly_renew_cookies + with_test_route_set do + get '/set_session_value' + get '/persistent_session_id' + session_id = response.body + get '/renew_session_id' + get '/persistent_session_id' + assert_not_equal response.body, session_id + end + end + def test_does_set_secure_cookies_over_https with_test_route_set(:secure => true) do get '/set_session_value', nil, 'HTTPS' => 'on' 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/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/activemodel/CHANGELOG b/activemodel/CHANGELOG index 03287fac7a..c26924cf09 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,6 +15,8 @@ * ActiveModel::AttributeMethods allows attributes to be defined on demand [Alexander Uvarov] +* Add support for selectively enabling/disabling observers [Myron Marston] + *Rails 3.0.2 (unreleased)* 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/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/activerecord/CHANGELOG b/activerecord/CHANGELOG index 9ff29f1155..a03751a6c1 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,20 @@ *Rails 3.1.0 (unreleased)* +* AR#new, AR#create and AR#update_attributes all accept a second hash as option that allows you + 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. + + [Josh Kalderimis] + * default_scope can take a block, lambda, or any other object which responds to `call` for lazy evaluation: @@ -22,25 +37,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.): 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/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/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/base.rb b/activerecord/lib/active_record/base.rb index 58a056bce9..78318b1be0 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -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. @@ -1943,32 +1947,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 +1957,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 +2023,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/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 96fea741e0..0e3ed7aac7 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -423,8 +423,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 +444,374 @@ 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 - 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 +837,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 +949,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 +962,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 +975,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 +984,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..f88ead9ca0 100644 --- a/activerecord/lib/active_record/identity_map.rb +++ b/activerecord/lib/active_record/identity_map.rb @@ -49,7 +49,7 @@ 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", @@ -64,15 +64,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/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/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..c6bc040f9f 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -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/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/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/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 007f11b535..247decc67b 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -605,6 +605,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/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb index e2228228a3..124693f7c9 100644 --- a/activerecord/test/cases/associations/inner_join_association_test.rb +++ b/activerecord/test/cases/associations/inner_join_association_test.rb @@ -34,6 +34,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/base_test.rb b/activerecord/test/cases/base_test.rb index 5ee3b2d776..1775ba9999 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -575,6 +575,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 +609,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 +791,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/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 3e20155210..2bf192e2c6 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,23 @@ 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_not_nil ActiveRecord::Fixtures.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/csv/accounts") 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 +220,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 +524,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 +538,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 +548,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/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/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/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/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/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/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/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..dd2beef89d 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -136,10 +136,10 @@ module ActiveSupport # ordinalize(1002) # => "1002nd" # ordinalize(1003) # => "1003rd" 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" diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index 82b8a7e148..1fafc36ee8 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -205,7 +205,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/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..d8f40ab8b1 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,10 @@ module ActiveSupport protected def _dasherize(key) - key.gsub(/(?!^[_]*)_(?![_]*$)/, '-') + left = key.strip.rpartition(/^_*/) + right = left.pop.partition(/_*$/) + right.first.tr!('_ ', '--') + left.concat(right).join end # TODO: Add support for other encodings 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..bf99a701a6 100644 --- a/activesupport/test/ordered_hash_test.rb +++ b/activesupport/test/ordered_hash_test.rb @@ -243,6 +243,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..310d86a019 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 diff --git a/railties/CHANGELOG b/railties/CHANGELOG index 0def5bcf6c..21666103de 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,5 +1,17 @@ *Rails 3.1.0 (unreleased)* +* 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" 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/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..ddd08a32ee 100644 --- a/railties/lib/rails/commands/runner.rb +++ b/railties/lib/rails/commands/runner.rb @@ -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 6c1064c609..2015a944f0 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -92,17 +92,17 @@ module Rails # The available paths in an engine are: # # class MyEngine < Rails::Engine - # paths["app"] #=> ["app"] - # paths["app/controllers"] #=> ["app/controllers"] - # paths["app/helpers"] #=> ["app/helpers"] - # paths["app/models"] #=> ["app/models"] - # paths["app/views"] #=> ["app/views"] - # paths["lib"] #=> ["lib"] - # paths["lib/tasks"] #=> ["lib/tasks"] - # paths["config"] #=> ["config"] - # paths["config/initializers"] #=> ["config/initializers"] - # paths["config/locales"] #=> ["config/locales"] - # paths["config/routes"] #=> ["config/routes.rb"] + # paths["app"] # => ["app"] + # paths["app/controllers"] # => ["app/controllers"] + # paths["app/helpers"] # => ["app/helpers"] + # paths["app/models"] # => ["app/models"] + # paths["app/views"] # => ["app/views"] + # paths["lib"] # => ["lib"] + # paths["lib/tasks"] # => ["lib/tasks"] + # paths["config"] # => ["config"] + # paths["config/initializers"] # => ["config/initializers"] + # paths["config/locales"] # => ["config/locales"] + # paths["config/routes"] # => ["config/routes.rb"] # end # # Your <tt>Application</tt> class adds a couple more paths to this set. And as in your @@ -234,14 +234,14 @@ module Rails # use the prefix "my_engine". In an isolated engine, the prefix will be omitted in url helpers and # form fields for convenience. # - # polymorphic_url(MyEngine::Article.new) #=> "articles_path" + # polymorphic_url(MyEngine::Article.new) # => "articles_path" # # form_for(MyEngine::Article.new) do - # text_field :title #=> <input type="text" name="article[title]" id="article_title" /> + # text_field :title # => <input type="text" name="article[title]" id="article_title" /> # end # - # Additionally isolated engine will set its name according to namespace, so - # MyEngine::Engine.engine_name #=> "my_engine". It will also set MyEngine.table_name_prefix + # Additionally an isolated engine will set its name according to namespace, so + # MyEngine::Engine.engine_name will be "my_engine". It will also set MyEngine.table_name_prefix # to "my_engine_", changing MyEngine::Article model to use my_engine_article table. # # == Using Engine's routes outside Engine @@ -250,7 +250,7 @@ module Rails # <tt>url_helpers</tt> inside +Application+. When you mount an engine in an application's routes, a special helper is # created to allow you to do that. Consider such a scenario: # - # # APP/config/routes.rb + # # config/routes.rb # MyApplication::Application.routes.draw do # mount MyEngine::Engine => "/my_engine", :as => "my_engine" # match "/foo" => "foo#index" 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..e8709b2ddd 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -117,15 +117,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 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/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index b046edd5b7..a69efdf29d 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -4,6 +4,7 @@ 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' 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/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index 3723addf2b..e1946807b0 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,10 +50,6 @@ 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" 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 60137ed2bb..32ffbee7a1 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 @@ -3,7 +3,7 @@ # This file contains the settings for ActionController::ParametersWrapper # which will be enabled by default in the upcoming version of Ruby on Rails. -# Enable parameter wrapping for JSON. You can disable this by set :format to empty array. +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. ActionController::Base.wrap_parameters :format => [:json] # Disable root element in JSON by default. diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb index d15887f561..5d217dcb10 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -31,23 +31,23 @@ module Rails # This means we can get a Path object back like below: # # path = root["app/controllers"] - # path.eager_load? #=> true - # path.is_a?(Rails::Paths::Path) #=> true + # path.eager_load? # => true + # path.is_a?(Rails::Paths::Path) # => true # # The Path object is simply an array and allows you to easily add extra paths: # - # path.is_a?(Array) #=> true - # path.inspect #=> ["app/controllers"] + # path.is_a?(Array) # => true + # path.inspect # => ["app/controllers"] # # path << "lib/controllers" - # path.inspect #=> ["app/controllers", "lib/controllers"] + # path.inspect # => ["app/controllers", "lib/controllers"] # # Notice that when you add a path using #add, the path object created already # contains the path with the same path value given to #add. In some situations, # you may not want this behavior, so you can give :with as option. # # root.add "config/routes", :with => "config/routes.rb" - # root["config/routes"].inspect #=> ["config/routes.rb"] + # root["config/routes"].inspect # => ["config/routes.rb"] # # #add also accepts the following options as argument: eager_load, autoload, # autoload_once and glob. @@ -58,8 +58,8 @@ module Rails # root.path = "/rails" # root.add "app/controllers" # - # root["app/controllers"].expanded #=> ["/rails/app/controllers"] - # root["app/controllers"].existent #=> ["/rails/app/controllers"] + # root["app/controllers"].expanded # => ["/rails/app/controllers"] + # root["app/controllers"].existent # => ["/rails/app/controllers"] # # Check the Path documentation for more information. class Root < ::Hash @@ -156,17 +156,17 @@ module Rails %w(autoload_once eager_load autoload load_path).each do |m| class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def #{m}! - @#{m} = true - end - - def skip_#{m}! - @#{m} = false - end - - def #{m}? - @#{m} - end + def #{m}! # def eager_load! + @#{m} = true # @eager_load = true + end # end + # + def skip_#{m}! # def skip_eager_load! + @#{m} = false # @eager_load = false + end # end + # + def #{m}? # def eager_load? + @#{m} # @eager_load + end # end RUBY end 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..6193e72625 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 @@ -437,10 +436,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/middleware_test.rb b/railties/test/application/middleware_test.rb index 01e6c49d9c..fd6dc46271 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -23,7 +23,8 @@ 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", @@ -36,7 +37,6 @@ module ApplicationTests "ActionDispatch::Session::CookieStore", "ActionDispatch::Flash", "ActionDispatch::ParamsParser", - "Rack::MethodOverride", "ActionDispatch::Head", "Rack::ConditionalGet", "Rack::ETag", 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..cc0bd53639 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 @@ -135,6 +137,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 +229,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 diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index 33c8d83f9c..f637a6a17e 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 diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb index c9c5d2fad2..03fd64ca38 100644 --- a/railties/test/generators/shared_generator_tests.rb +++ b/railties/test/generators/shared_generator_tests.rb @@ -1,3 +1,6 @@ +# +# Tests, setup, and teardown common to the application and plugin generator suites. +# module SharedGeneratorTests def setup Rails.application = TestApp::Application 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 |