diff options
author | Pratik Naik <pratiknaik@gmail.com> | 2008-10-16 22:20:32 +0200 |
---|---|---|
committer | Pratik Naik <pratiknaik@gmail.com> | 2008-10-16 22:20:32 +0200 |
commit | 0242d817035e60357e559af0e9441a1f24a764e4 (patch) | |
tree | 1efa579fc261e55f76bacfa9818eb4a649d68294 | |
parent | 7e2777348b88bf096ebc51e364554af19d7bcc55 (diff) | |
parent | 9cb5400871b660e2c6d1654346650f07bb52a0c0 (diff) | |
download | rails-0242d817035e60357e559af0e9441a1f24a764e4.tar.gz rails-0242d817035e60357e559af0e9441a1f24a764e4.tar.bz2 rails-0242d817035e60357e559af0e9441a1f24a764e4.zip |
Merge with mainstream rails
76 files changed, 819 insertions, 150 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 54ea93fb72..c68bfc753c 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,15 @@ *Edge* +* Added inline builder yield to atom_feed_helper tags where appropriate [Sam Ruby]. Example: + + entry.summary :type => 'xhtml' do |xhtml| + xhtml.p pluralize(order.line_items.count, "line item") + xhtml.p "Shipped to #{order.address}" + xhtml.p "Paid by #{order.pay_type}" + end + +* Make PrototypeHelper#submit_to_remote a wrapper around PrototypeHelper#button_to_remote. [Tarmo Tänav] + * Set HttpOnly for the cookie session store's cookie. #1046 * Added FormTagHelper#image_submit_tag confirm option #784 [Alastair Brunton] diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 413f6d48e5..3ede681253 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -969,7 +969,9 @@ module ActionController #:nodoc: # If-Modified-Since request header is <= last modified. def last_modified!(utc_time) response.last_modified= utc_time - head(:not_modified) if response.last_modified == request.if_modified_since + if request.if_modified_since && request.if_modified_since <= utc_time + head(:not_modified) + end end # Sets the ETag response header. Returns 304 Not Modified if the diff --git a/actionpack/lib/action_controller/routing/builder.rb b/actionpack/lib/action_controller/routing/builder.rb index 5704d9d01a..7b888fa8d2 100644 --- a/actionpack/lib/action_controller/routing/builder.rb +++ b/actionpack/lib/action_controller/routing/builder.rb @@ -60,12 +60,10 @@ module ActionController # segments are passed alongside in order to distinguish between default values # and requirements. def divide_route_options(segments, options) - options = options.dup + options = options.except(:path_prefix, :name_prefix) if options[:namespace] options[:controller] = "#{options.delete(:namespace).sub(/\/$/, '')}/#{options[:controller]}" - options.delete(:path_prefix) - options.delete(:name_prefix) end requirements = (options.delete(:requirements) || {}).dup diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 6a39039504..4fc60f0697 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -134,6 +134,9 @@ module ActionController @controller = self.class.controller_class.new @controller.request = @request = TestRequest.new @response = TestResponse.new + + @controller.params = {} + @controller.send(:initialize_current_url) end # Cause the action to be rescued according to the regular rules for rescue_action when the visitor is not local diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 63ccde393a..93d38eb929 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -596,7 +596,7 @@ module ActionView end def missing_extension?(source) - extension && File.extname(source).blank? || File.exist?(File.join(ASSETS_DIR, directory, "#{source}.#{extension}")) + extension && (File.extname(source).blank? || File.exist?(File.join(ASSETS_DIR, directory, "#{source}.#{extension}"))) end def prepend_relative_url_root(source) diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb index e65d5d1f60..ccb7df212a 100644 --- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb +++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb @@ -51,6 +51,7 @@ module ActionView # * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you # created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified, # 2005 is used (as an "I don't care" value). + # * <tt>:instruct</tt>: Hash of XML processing instructions in the form {target => {attribute => value, }} or {target => [{attribute => value, }, ]} # # Other namespaces can be added to the root element: # @@ -74,8 +75,20 @@ module ActionView # end # end # + # The Atom spec defines five elements (content rights title subtitle + # summary) which may directly contain xhtml content if :type => 'xhtml' + # is specified as an attribute. If so, this helper will take care of + # the enclosing div and xhtml namespace declaration. Example usage: # - # atom_feed yields an AtomFeedBuilder instance. + # entry.summary :type => 'xhtml' do |xhtml| + # xhtml.p pluralize(order.line_items.count, "line item") + # xhtml.p "Shipped to #{order.address}" + # xhtml.p "Paid by #{order.pay_type}" + # end + # + # + # atom_feed yields an AtomFeedBuilder instance. Nested elements yield + # an AtomBuilder instance. def atom_feed(options = {}, &block) if options[:schema_date] options[:schema_date] = options[:schema_date].strftime("%Y-%m-%d") if options[:schema_date].respond_to?(:strftime) @@ -85,6 +98,15 @@ module ActionView xml = options[:xml] || eval("xml", block.binding) xml.instruct! + if options[:instruct] + options[:instruct].each do |target,attrs| + if attrs.respond_to?(:keys) + xml.instruct!(target, attrs) + elsif attrs.respond_to?(:each) + attrs.each { |attr_group| xml.instruct!(target, attr_group) } + end + end + end feed_opts = {"xml:lang" => options[:language] || "en-US", "xmlns" => 'http://www.w3.org/2005/Atom'} feed_opts.merge!(options).reject!{|k,v| !k.to_s.match(/^xml/)} @@ -98,8 +120,38 @@ module ActionView end end + class AtomBuilder + def initialize(xml) + @xml = xml + end + + private + # Delegate to xml builder, first wrapping the element in a xhtml + # namespaced div element if the method and arguments indicate + # that an xhtml_block? is desired. + def method_missing(method, *arguments, &block) + if xhtml_block?(method, arguments) + @xml.__send__(method, *arguments) do + @xml.div(:xmlns => 'http://www.w3.org/1999/xhtml') do |xhtml| + block.call(xhtml) + end + end + else + @xml.__send__(method, *arguments, &block) + end + end + + # True if the method name matches one of the five elements defined + # in the Atom spec as potentially containing XHTML content and + # if :type => 'xhtml' is, in fact, specified. + def xhtml_block?(method, arguments) + %w( content rights title subtitle summary ).include?(method.to_s) && + arguments.last.respond_to?(:[]) && + arguments.last[:type].to_s == 'xhtml' + end + end - class AtomFeedBuilder + class AtomFeedBuilder < AtomBuilder def initialize(xml, view, feed_options = {}) @xml, @view, @feed_options = xml, view, feed_options end @@ -131,15 +183,11 @@ module ActionView @xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:url] || @view.polymorphic_url(record)) - yield @xml + yield AtomBuilder.new(@xml) end end - - private - def method_missing(method, *arguments, &block) - @xml.__send__(method, *arguments, &block) - end end + end end end diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 208bf91dd4..7492348c50 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -62,7 +62,7 @@ module ActionView # # <option>3</option><option>4</option></select> # # select_tag "colors", "<option>Red</option><option>Green</option><option>Blue</option>", :multiple => true - # # => <select id="colors" multiple="multiple" name="colors"><option>Red</option> + # # => <select id="colors" multiple="multiple" name="colors[]"><option>Red</option> # # <option>Green</option><option>Blue</option></select> # # select_tag "locations", "<option>Home</option><option selected="selected">Work</option><option>Out</option>" @@ -70,14 +70,15 @@ module ActionView # # <option>Out</option></select> # # select_tag "access", "<option>Read</option><option>Write</option>", :multiple => true, :class => 'form_input' - # # => <select class="form_input" id="access" multiple="multiple" name="access"><option>Read</option> + # # => <select class="form_input" id="access" multiple="multiple" name="access[]"><option>Read</option> # # <option>Write</option></select> # # select_tag "destination", "<option>NYC</option><option>Paris</option><option>Rome</option>", :disabled => true # # => <select disabled="disabled" id="destination" name="destination"><option>NYC</option> # # <option>Paris</option><option>Rome</option></select> def select_tag(name, option_tags = nil, options = {}) - content_tag :select, option_tags, { "name" => name, "id" => name }.update(options.stringify_keys) + html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name + content_tag :select, option_tags, { "name" => html_name, "id" => name }.update(options.stringify_keys) end # Creates a standard text field; use these text fields to input smaller chunks of text like a username diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index ff41a6d417..a3eccc741d 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -405,7 +405,7 @@ module ActionView # # Generates: <input name="create_btn" onclick="new Ajax.Request('/testing/create', # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)}); # # return false;" type="button" value="Create" /> - # <%= button_to_remote 'create_btn', 'Create', :url => { :action => 'create' } %> + # <%= submit_to_remote 'create_btn', 'Create', :url => { :action => 'create' } %> # # # Submit to the remote action update and update the DIV succeed or fail based # # on the success or failure of the request @@ -413,24 +413,18 @@ module ActionView # # Generates: <input name="update_btn" onclick="new Ajax.Updater({success:'succeed',failure:'fail'}, # # '/testing/update', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)}); # # return false;" type="button" value="Update" /> - # <%= button_to_remote 'update_btn', 'Update', :url => { :action => 'update' }, + # <%= submit_to_remote 'update_btn', 'Update', :url => { :action => 'update' }, # :update => { :success => "succeed", :failure => "fail" } # # <tt>options</tt> argument is the same as in form_remote_tag. - # - # Note: This method used to be called submit_to_remote, but that's now just an alias for button_to_remote - def button_to_remote(name, value, options = {}) + def submit_to_remote(name, value, options = {}) options[:with] ||= 'Form.serialize(this.form)' - options[:html] ||= {} - options[:html][:type] = 'button' - options[:html][:onclick] = "#{remote_function(options)}; return false;" - options[:html][:name] = name - options[:html][:value] = value + html_options = options.delete(:html) || {} + html_options[:name] = name - tag("input", options[:html], false) + button_to_remote(value, options, html_options) end - alias_method :submit_to_remote, :button_to_remote # Returns '<tt>eval(request.responseText)</tt>' which is the JavaScript function # that +form_remote_tag+ can call in <tt>:complete</tt> to evaluate a multiple diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 5a6ca98b2e..7b8bb6856b 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -1441,6 +1441,12 @@ class LastModifiedRenderTest < Test::Unit::TestCase get :conditional_hello_with_bangs assert_response :not_modified end + + def test_last_modified_works_with_less_than_too + @request.if_modified_since = 5.years.ago.httpdate + get :conditional_hello_with_bangs + assert_response :not_modified + end end class RenderingLoggingTest < Test::Unit::TestCase diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 1eb26a7cfd..9699a04abb 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -924,6 +924,20 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do end + def test_named_route_with_name_prefix + rs.add_named_route :page, 'page', :controller => 'content', :action => 'show_page', :name_prefix => 'my_' + x = setup_for_named_route + assert_equal("http://named.route.test/page", + x.send(:my_page_url)) + end + + def test_named_route_with_path_prefix + rs.add_named_route :page, 'page', :controller => 'content', :action => 'show_page', :path_prefix => 'my' + x = setup_for_named_route + assert_equal("http://named.route.test/my/page", + x.send(:page_url)) + end + def test_named_route_with_nested_controller rs.add_named_route :users, 'admin/user', :controller => 'admin/user', :action => 'index' x = setup_for_named_route @@ -2147,6 +2161,13 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do assert_equal [:x], set.extra_keys(args) end + def test_generate_with_path_prefix + set.draw { |map| map.connect ':controller/:action/:id', :path_prefix => 'my' } + + args = { :controller => "foo", :action => "bar", :id => "7", :x => "y" } + assert_equal "/my/foo/bar/7?x=y", set.generate(args) + end + def test_named_routes_are_never_relative_to_modules set.draw do |map| map.connect "/connection/manage/:action", :controller => 'connection/manage' diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb index 9eff34a542..a23428804a 100644 --- a/actionpack/test/controller/test_test.rb +++ b/actionpack/test/controller/test_test.rb @@ -667,6 +667,7 @@ class NamedRoutesControllerTest < ActionController::TestCase with_routing do |set| set.draw { |map| map.resources :contents } assert_equal 'http://test.host/contents/new', new_content_url + assert_equal 'http://test.host/contents/1', content_url(:id => 1) end end end diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index aaf9fe2ebf..6dc1225035 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -230,6 +230,19 @@ class AssetTagHelperTest < ActionView::TestCase ImageLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end + uses_mocha 'test image tag with windows behaviour' do + def test_image_tag_windows_behaviour + old_asset_id, ENV["RAILS_ASSET_ID"] = ENV["RAILS_ASSET_ID"], "1" + # This simulates the behaviour of File#exist? on windows when testing a file ending in "." + # If the file "rails.png" exists, windows will return true when asked if "rails.png." exists (notice trailing ".") + # OS X, linux etc will return false in this case. + File.stubs(:exist?).with('template/../fixtures/public/images/rails.png.').returns(true) + assert_equal '<img alt="Rails" src="/images/rails.png?1" />', image_tag('rails.png') + ensure + ENV["RAILS_ASSET_ID"] = old_asset_id + end + end + def test_timebased_asset_id expected_time = File.stat(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).mtime.to_i.to_s assert_equal %(<img alt="Rails" src="/images/rails.png?#{expected_time}" />), image_tag("rails.png") diff --git a/actionpack/test/template/atom_feed_helper_test.rb b/actionpack/test/template/atom_feed_helper_test.rb index ef31ab2c76..9247a42d33 100644 --- a/actionpack/test/template/atom_feed_helper_test.rb +++ b/actionpack/test/template/atom_feed_helper_test.rb @@ -92,6 +92,64 @@ class ScrollsController < ActionController::Base end end EOT + FEEDS["feed_with_xml_processing_instructions"] = <<-EOT + atom_feed(:schema_date => '2008', + :instruct => {'xml-stylesheet' => { :href=> 't.css', :type => 'text/css' }}) do |feed| + feed.title("My great blog!") + feed.updated((@scrolls.first.created_at)) + + for scroll in @scrolls + feed.entry(scroll) do |entry| + entry.title(scroll.title) + entry.content(scroll.body, :type => 'html') + + entry.author do |author| + author.name("DHH") + end + end + end + end + EOT + FEEDS["feed_with_xml_processing_instructions_duplicate_targets"] = <<-EOT + atom_feed(:schema_date => '2008', + :instruct => {'target1' => [{ :a => '1', :b => '2' }, { :c => '3', :d => '4' }]}) do |feed| + feed.title("My great blog!") + feed.updated((@scrolls.first.created_at)) + + for scroll in @scrolls + feed.entry(scroll) do |entry| + entry.title(scroll.title) + entry.content(scroll.body, :type => 'html') + + entry.author do |author| + author.name("DHH") + end + end + end + end + EOT + FEEDS["feed_with_xhtml_content"] = <<-'EOT' + atom_feed do |feed| + feed.title("My great blog!") + feed.updated((@scrolls.first.created_at)) + + for scroll in @scrolls + feed.entry(scroll) do |entry| + entry.title(scroll.title) + entry.summary(:type => 'xhtml') do |xhtml| + xhtml.p "before #{scroll.id}" + xhtml.p {xhtml << scroll.body} + xhtml.p "after #{scroll.id}" + end + entry.tag!('app:edited', Time.now) + + entry.author do |author| + author.name("DHH") + end + end + end + end + EOT def index @scrolls = [ Scroll.new(1, "1", "Hello One", "Something <i>COOL!</i>", Time.utc(2007, 12, 12, 15), Time.utc(2007, 12, 12, 15)), @@ -194,6 +252,29 @@ class AtomFeedTest < Test::Unit::TestCase end end + def test_feed_xml_processing_instructions + with_restful_routing(:scrolls) do + get :index, :id => 'feed_with_xml_processing_instructions' + assert_match %r{<\?xml-stylesheet type="text/css" href="t.css"\?>}, @response.body + end + end + + def test_feed_xml_processing_instructions_duplicate_targets + with_restful_routing(:scrolls) do + get :index, :id => 'feed_with_xml_processing_instructions_duplicate_targets' + assert_match %r{<\?target1 (a="1" b="2"|b="2" a="1")\?>}, @response.body + assert_match %r{<\?target1 (c="3" d="4"|d="4" c="3")\?>}, @response.body + end + end + + def test_feed_xhtml + with_restful_routing(:scrolls) do + get :index, :id => "feed_with_xhtml_content" + assert_match %r{xmlns="http://www.w3.org/1999/xhtml"}, @response.body + assert_select "summary div p", :text => "Something Boring" + assert_select "summary div p", :text => "after 2" + end + end private def with_restful_routing(resources) with_routing do |set| diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index ad8baef5e4..1849a61f2f 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -211,7 +211,8 @@ class FormTagHelperTest < ActionView::TestCase def test_boolean_optios assert_dom_equal %(<input checked="checked" disabled="disabled" id="admin" name="admin" readonly="readonly" type="checkbox" value="1" />), check_box_tag("admin", 1, true, 'disabled' => true, :readonly => "yes") assert_dom_equal %(<input checked="checked" id="admin" name="admin" type="checkbox" value="1" />), check_box_tag("admin", 1, true, :disabled => false, :readonly => nil) - assert_dom_equal %(<select id="people" multiple="multiple" name="people"><option>david</option></select>), select_tag("people", "<option>david</option>", :multiple => true) + assert_dom_equal %(<select id="people" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people", "<option>david</option>", :multiple => true) + assert_dom_equal %(<select id="people[]" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people[]", "<option>david</option>", :multiple => true) assert_dom_equal %(<select id="people" name="people"><option>david</option></select>), select_tag("people", "<option>david</option>", :multiple => nil) end diff --git a/actionpack/test/template/prototype_helper_test.rb b/actionpack/test/template/prototype_helper_test.rb index a1f541fd7b..d6b86a3964 100644 --- a/actionpack/test/template/prototype_helper_test.rb +++ b/actionpack/test/template/prototype_helper_test.rb @@ -218,9 +218,9 @@ class PrototypeHelperTest < PrototypeHelperBaseTest end - def test_button_to_remote - assert_dom_equal %(<input name=\"More beer!\" onclick=\"new Ajax.Updater('empty_bottle', 'http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)}); return false;\" type=\"button\" value=\"1000000\" />), - button_to_remote("More beer!", 1_000_000, :update => "empty_bottle") + def test_submit_to_remote + assert_dom_equal %(<input name=\"More beer!\" onclick=\"new Ajax.Updater('empty_bottle', 'http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});\" type=\"button\" value=\"1000000\" />), + submit_to_remote("More beer!", 1_000_000, :update => "empty_bottle") end def test_observe_field diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index 99e3973830..6e194ab9b4 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -193,6 +193,7 @@ module ActiveRecord end def preload_has_one_association(records, reflection, preload_options={}) + return if records.first.send("loaded_#{reflection.name}?") id_to_record_map, ids = construct_id_map(records) options = reflection.options records.each {|record| record.send("set_#{reflection.name}_target", nil)} @@ -214,6 +215,7 @@ module ActiveRecord end def preload_has_many_association(records, reflection, preload_options={}) + return if records.first.send(reflection.name).loaded? options = reflection.options primary_key_name = reflection.through_reflection_primary_key_name @@ -271,6 +273,7 @@ module ActiveRecord end def preload_belongs_to_association(records, reflection, preload_options={}) + return if records.first.send("loaded_#{reflection.name}?") options = reflection.options primary_key_name = reflection.primary_key_name diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index d1a0b2f96a..ad093d83d4 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1235,7 +1235,7 @@ module ActiveRecord association = instance_variable_get(ivar) if instance_variable_defined?(ivar) - if association.nil? || !association.loaded? || force_reload + if association.nil? || force_reload association = association_proxy_class.new(self, reflection) retval = association.reload if retval.nil? and association_proxy_class == BelongsToAssociation @@ -1248,6 +1248,11 @@ module ActiveRecord association.target.nil? ? nil : association end + define_method("loaded_#{reflection.name}?") do + association = instance_variable_get(ivar) if instance_variable_defined?(ivar) + association && association.loaded? + end + define_method("#{reflection.name}=") do |new_value| association = instance_variable_get(ivar) if instance_variable_defined?(ivar) @@ -1264,17 +1269,6 @@ module ActiveRecord end end - if association_proxy_class == BelongsToAssociation - define_method("#{reflection.primary_key_name}=") do |target_id| - if instance_variable_defined?(ivar) - if association = instance_variable_get(ivar) - association.reset - end - end - write_attribute(reflection.primary_key_name, target_id) - end - end - define_method("set_#{reflection.name}_target") do |target| return if target.nil? and association_proxy_class == BelongsToAssociation association = association_proxy_class.new(self, reflection) diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 463de9d819..09a80be266 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -320,6 +320,10 @@ module ActiveRecord exists?(record) end + def proxy_respond_to?(method) + super || @reflection.klass.respond_to?(method) + end + protected def construct_find_options!(options) end diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index b617147f67..d1a79df6e6 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -140,6 +140,15 @@ module ActiveRecord @target.inspect end + def send(method, *args) + if proxy_respond_to?(method) + super + else + load_target + @target.send(method, *args) + end + end + protected # Does the association have a <tt>:dependent</tt> option? def dependent? @@ -197,6 +206,8 @@ module ActiveRecord # Forwards any missing method call to the \target. def method_missing(method, *args) if load_target + raise NoMethodError unless @target.respond_to?(method) + if block_given? @target.send(method, *args) { |*block_args| yield(*block_args) } else diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index c92ef5c2c9..960323004d 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -57,7 +57,7 @@ module ActiveRecord protected def owner_quoted_id if @reflection.options[:primary_key] - quote_value(@owner.send(@reflection.options[:primary_key])) + @owner.class.quote_value(@owner.send(@reflection.options[:primary_key])) else @owner.quoted_id end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 10dc1a81f3..97c6cd4331 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -120,10 +120,6 @@ module ActiveRecord sql end - def sanitize_limit(limit) - limit.to_s[/,/] ? limit.split(',').map{ |i| i.to_i }.join(',') : limit.to_i - end - # Appends a locking clause to an SQL statement. # This method *modifies* the +sql+ parameter. # # SELECT * FROM suppliers FOR UPDATE @@ -185,6 +181,21 @@ module ActiveRecord def delete_sql(sql, name = nil) update_sql(sql, name) end + + # Sanitizes the given LIMIT parameter in order to prevent SQL injection. + # + # +limit+ may be anything that can evaluate to a string via #to_s. It + # should look like an integer, or a comma-delimited list of integers. + # + # Returns the sanitized limit parameter, either as an integer, or as a + # string which contains a comma-delimited list of integers. + def sanitize_limit(limit) + if limit.to_s =~ /,/ + limit.to_s.split(',').map{ |i| i.to_i }.join(',') + else + limit.to_i + end + end end end end diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 37b6836a89..40a8503980 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -47,19 +47,6 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal apple.id, citibank.firm_id end - def test_foreign_key_assignment - # Test using an existing record - signals37 = accounts(:signals37) - assert_equal companies(:first_firm), signals37.firm - signals37.firm_id = companies(:another_firm).id - assert_equal companies(:another_firm), signals37.firm - - # Test using a new record - account = Account.new - account.firm_id = companies(:another_firm).id - assert_equal companies(:another_firm), account.firm - end - def test_no_unexpected_aliasing first_firm = companies(:first_firm) another_firm = companies(:another_firm) @@ -441,4 +428,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert log.valid? assert log.save end + + def test_belongs_to_proxy_should_not_respond_to_private_methods + assert_raises(NoMethodError) { companies(:first_firm).private_method } + assert_raises(NoMethodError) { companies(:second_client).firm.private_method } + end + + def test_belongs_to_proxy_should_respond_to_private_methods_via_send + companies(:first_firm).send(:private_method) + companies(:second_client).firm.send(:private_method) + end end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 7f42577ab0..5f43975d5a 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -18,7 +18,7 @@ require 'models/developer' require 'models/project' class EagerAssociationTest < ActiveRecord::TestCase - fixtures :posts, :comments, :authors, :categories, :categories_posts, + fixtures :posts, :comments, :authors, :author_addresses, :categories, :categories_posts, :companies, :accounts, :tags, :taggings, :people, :readers, :owners, :pets, :author_favorites, :jobs, :references, :subscribers, :subscriptions, :books, :developers, :projects, :developers_projects @@ -111,6 +111,46 @@ class EagerAssociationTest < ActiveRecord::TestCase end end + def test_finding_with_includes_on_has_many_association_with_same_include_includes_only_once + author_id = authors(:david).id + author = assert_queries(3) { Author.find(author_id, :include => {:posts_with_comments => :comments}) } # find the author, then find the posts, then find the comments + author.posts_with_comments.each do |post_with_comments| + assert_equal post_with_comments.comments.length, post_with_comments.comments.count + assert_equal nil, post_with_comments.comments.uniq! + end + end + + def test_finding_with_includes_on_has_one_assocation_with_same_include_includes_only_once + author = authors(:david) + post = author.post_about_thinking_with_last_comment + last_comment = post.last_comment + author = assert_queries(3) { Author.find(author.id, :include => {:post_about_thinking_with_last_comment => :last_comment})} # find the author, then find the posts, then find the comments + assert_no_queries do + assert_equal post, author.post_about_thinking_with_last_comment + assert_equal last_comment, author.post_about_thinking_with_last_comment.last_comment + end + end + + def test_finding_with_includes_on_belongs_to_association_with_same_include_includes_only_once + post = posts(:welcome) + author = post.author + author_address = author.author_address + post = assert_queries(3) { Post.find(post.id, :include => {:author_with_address => :author_address}) } # find the post, then find the author, then find the address + assert_no_queries do + assert_equal author, post.author_with_address + assert_equal author_address, post.author_with_address.author_address + end + end + + def test_finding_with_includes_on_null_belongs_to_association_with_same_include_includes_only_once + post = posts(:welcome) + post.update_attributes!(:author => nil) + post = assert_queries(2) { Post.find(post.id, :include => {:author_with_address => :author_address}) } # find the post, then find the author which is null so no query for the address + assert_no_queries do + assert_equal nil, post.author_with_address + end + end + def test_loading_from_an_association posts = authors(:david).posts.find(:all, :include => :comments, :order => "posts.id") assert_equal 2, posts.first.comments.size diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 1bc9c39c19..dada2d1603 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -1080,5 +1080,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end end + def test_sending_new_to_association_proxy_should_have_same_effect_as_calling_new + clients_assoc = companies(:first_firm).clients + assert_equal clients_assoc.new.attributes, clients_assoc.send(:new).attributes + end end diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index ec06be5eba..14032a67c0 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -349,4 +349,14 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert companies(:first_firm).readonly_account.readonly? end + def test_has_one_proxy_should_not_respond_to_private_methods + assert_raises(NoMethodError) { accounts(:signals37).private_method } + assert_raises(NoMethodError) { companies(:first_firm).account.private_method } + end + + def test_has_one_proxy_should_respond_to_private_methods_via_send + accounts(:signals37).send(:private_method) + companies(:first_firm).account.send(:private_method) + end + end diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb index 77e3cb1776..ff4021fe02 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -110,4 +110,14 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase new_member.club = new_club = Club.create(:name => "LRUG") assert_equal new_club, new_member.club.target end + + def test_has_one_through_proxy_should_not_respond_to_private_methods + assert_raises(NoMethodError) { clubs(:moustache_club).private_method } + assert_raises(NoMethodError) { @member.club.private_method } + end + + def test_has_one_through_proxy_should_respond_to_private_methods_via_send + clubs(:moustache_club).send(:private_method) + @member.club.send(:private_method) + end end diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index cde451de0e..056a29438a 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -182,26 +182,6 @@ class AssociationProxyTest < ActiveRecord::TestCase assert_nil p.author.reset end - def test_reset_loads_association_next_time - welcome = posts(:welcome) - david = authors(:david) - author_assoc = welcome.author - - assert_equal david, welcome.author # So we can be sure the test works correctly - author_assoc.reset - assert !author_assoc.loaded? - assert_nil author_assoc.target - assert_equal david, welcome.author - end - - def test_assigning_association_id_after_reload - welcome = posts(:welcome) - welcome.reload - assert_nothing_raised do - welcome.author_id = authors(:david).id - end - end - def test_reload_returns_assocition david = developers(:david) assert_nothing_raised do diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index 37551c8157..e5b19ff9e4 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -17,6 +17,8 @@ class Author < ActiveRecord::Base proxy_target end end + has_one :post_about_thinking, :class_name => 'Post', :conditions => "posts.title like '%thinking%'" + has_one :post_about_thinking_with_last_comment, :class_name => 'Post', :conditions => "posts.title like '%thinking%'", :include => :last_comment has_many :comments, :through => :posts has_many :comments_containing_the_letter_e, :through => :posts, :source => :comments has_many :comments_with_order_and_conditions, :through => :posts, :source => :comments, :order => 'comments.body', :conditions => "comments.body like 'Thank%'" diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb index 3ddb691dfb..6e7cdd643a 100644 --- a/activerecord/test/models/club.rb +++ b/activerecord/test/models/club.rb @@ -4,4 +4,10 @@ class Club < ActiveRecord::Base has_many :current_memberships has_one :sponsor has_one :sponsored_member, :through => :sponsor, :source => :sponsorable, :source_type => "Member" + + private + + def private_method + "I'm sorry sir, this is a *private* club, not a *pirate* club" + end end
\ No newline at end of file diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 0eb8ae0a15..62d20861f3 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -13,6 +13,12 @@ class Company < AbstractCompany def arbitrary_method "I am Jack's profound disappointment" end + + private + + def private_method + "I am Jack's innermost fears and aspirations" + end end module Namespaced @@ -129,9 +135,14 @@ class Account < ActiveRecord::Base true end - protected def validate errors.add_on_empty "credit_limit" end + + private + + def private_method + "Sir, yes sir!" + end end diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 6da37c31ff..e0d8be676a 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -13,6 +13,7 @@ class Post < ActiveRecord::Base end belongs_to :author_with_posts, :class_name => "Author", :foreign_key => :author_id, :include => :posts + belongs_to :author_with_address, :class_name => "Author", :foreign_key => :author_id, :include => :author_address has_one :last_comment, :class_name => 'Comment', :order => 'id desc' diff --git a/activeresource/CHANGELOG b/activeresource/CHANGELOG index 012f9028a0..e93b3ec15b 100644 --- a/activeresource/CHANGELOG +++ b/activeresource/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* Add ActiveResource::Base#to_xml and ActiveResource::Base#to_json. #1011 [Rasik Pandey, Cody Fauser] + * Add ActiveResource::Base.find(:last). [#754 state:resolved] (Adrian Mugnolo) * Fixed problems with the logger used if the logging string included %'s [#840 state:resolved] (Jamis Buck) diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index 74d8128c0e..bb284803d8 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -854,6 +854,42 @@ module ActiveResource # # my_group.to_xml(:skip_instruct => true) # # => <subsidiary_group> [...] </subsidiary_group> + def to_xml(options={}) + attributes.to_xml({:root => self.class.element_name}.merge(options)) + end + + # Returns a JSON string representing the model. Some configuration is + # available through +options+. + # + # ==== Options + # The +options+ are passed to the +to_json+ method on each + # attribute, so the same options as the +to_json+ methods in + # Active Support. + # + # * <tt>:only</tt> - Only include the specified attribute or list of + # attributes in the serialized output. Attribute names must be specified + # as strings. + # * <tt>:except</tt> - Do not include the specified attribute or list of + # attributes in the serialized output. Attribute names must be specified + # as strings. + # + # ==== Examples + # person = Person.new(:first_name => "Jim", :last_name => "Smith") + # person.to_json + # # => {"first_name": "Jim", "last_name": "Smith"} + # + # person.to_json(:only => ["first_name"]) + # # => {"first_name": "Jim"} + # + # person.to_json(:except => ["first_name"]) + # # => {"last_name": "Smith"} + def to_json(options={}) + attributes.to_json(options) + end + + # Returns the serialized string representation of the resource in the configured + # serialization format specified in ActiveResource::Base.format. The options + # applicable depend on the configured encoding format. def encode(options={}) case self.class.format when ActiveResource::Formats[:xml] diff --git a/activeresource/lib/active_resource/formats/json_format.rb b/activeresource/lib/active_resource/formats/json_format.rb index 9e269d4ded..1d88fc5f16 100644 --- a/activeresource/lib/active_resource/formats/json_format.rb +++ b/activeresource/lib/active_resource/formats/json_format.rb @@ -12,7 +12,7 @@ module ActiveResource end def encode(hash, options={}) - hash.to_json + hash.to_json(options) end def decode(json) diff --git a/activeresource/test/format_test.rb b/activeresource/test/format_test.rb index 365576a092..a564ee01b5 100644 --- a/activeresource/test/format_test.rb +++ b/activeresource/test/format_test.rb @@ -1,5 +1,6 @@ require 'abstract_unit' require "fixtures/person" +require "fixtures/street_address" class FormatTest < Test::Unit::TestCase def setup @@ -83,6 +84,22 @@ class FormatTest < Test::Unit::TestCase assert_equal ActiveResource::Formats[:json], resource.connection.format end + def test_serialization_of_nested_resource + address = { :street => '12345 Street' } + person = { :name=> 'Rus', :address => address} + + [:json, :xml].each do |format| + encoded_person = ActiveResource::Formats[format].encode(person) + assert_match /12345 Street/, encoded_person + remote_person = Person.new(person.update({:address => StreetAddress.new(address)})) + assert_kind_of StreetAddress, remote_person.address + using_format(Person, format) do + ActiveResource::HttpMock.respond_to.post "/people.#{format}", {'Content-Type' => ActiveResource::Formats[format].mime_type}, encoded_person, 201, {'Location' => "/people/5.#{format}"} + remote_person.save + end + end + end + private def using_format(klass, mime_type_reference) previous_format = klass.format diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 2c6f4ed582..08bb632448 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* Time#advance recognizes fractional days and weeks. Deprecate Durations of fractional months and years #970 [Tom Lea] + * Add ActiveSupport::Rescuable module abstracting ActionController::Base rescue_from features. [Norbert Crombach, Pratik] * Switch from String#chars to String#mb_chars for the unicode proxy. [Manfred Stienstra] diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index e67b719ddb..11c128da22 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -11,7 +11,8 @@ module ActiveSupport #:nodoc: options.assert_valid_keys(:connector, :skip_last_comma, :locale) default = I18n.translate(:'support.array.sentence_connector', :locale => options[:locale]) - options.reverse_merge! :connector => default, :skip_last_comma => false + default_skip_last_comma = I18n.translate(:'support.array.skip_last_comma', :locale => options[:locale]) + options.reverse_merge! :connector => default, :skip_last_comma => default_skip_last_comma options[:connector] = "#{options[:connector]} " unless options[:connector].nil? || options[:connector].strip == '' case length diff --git a/activesupport/lib/active_support/core_ext/float.rb b/activesupport/lib/active_support/core_ext/float.rb index 86862b7150..af166aa610 100644 --- a/activesupport/lib/active_support/core_ext/float.rb +++ b/activesupport/lib/active_support/core_ext/float.rb @@ -1,5 +1,7 @@ require 'active_support/core_ext/float/rounding' +require 'active_support/core_ext/float/time' class Float #:nodoc: include ActiveSupport::CoreExtensions::Float::Rounding + include ActiveSupport::CoreExtensions::Float::Time end diff --git a/activesupport/lib/active_support/core_ext/float/time.rb b/activesupport/lib/active_support/core_ext/float/time.rb new file mode 100644 index 0000000000..13f2e0ddca --- /dev/null +++ b/activesupport/lib/active_support/core_ext/float/time.rb @@ -0,0 +1,27 @@ +module ActiveSupport #:nodoc: + module CoreExtensions #:nodoc: + module Float #:nodoc: + module Time + # Deprication helper methods not available as core_ext is loaded first. + def years + ::ActiveSupport::Deprecation.warn(self.class.deprecated_method_warning(:years, "Fractional years are not respected. Convert value to integer before calling #years."), caller) + years_without_deprecation + end + def months + ::ActiveSupport::Deprecation.warn(self.class.deprecated_method_warning(:months, "Fractional months are not respected. Convert value to integer before calling #months."), caller) + months_without_deprecation + end + + def months_without_deprecation + ActiveSupport::Duration.new(self * 30.days, [[:months, self]]) + end + alias :month :months + + def years_without_deprecation + ActiveSupport::Duration.new(self * 365.25.days, [[:years, self]]) + end + alias :year :years + end + end + end +end
\ No newline at end of file diff --git a/activesupport/lib/active_support/core_ext/integer.rb b/activesupport/lib/active_support/core_ext/integer.rb index d1e6d76acb..18a7a402db 100644 --- a/activesupport/lib/active_support/core_ext/integer.rb +++ b/activesupport/lib/active_support/core_ext/integer.rb @@ -1,7 +1,9 @@ require 'active_support/core_ext/integer/even_odd' require 'active_support/core_ext/integer/inflections' +require 'active_support/core_ext/integer/time' class Integer #:nodoc: include ActiveSupport::CoreExtensions::Integer::EvenOdd include ActiveSupport::CoreExtensions::Integer::Inflections + include ActiveSupport::CoreExtensions::Integer::Time end diff --git a/activesupport/lib/active_support/core_ext/integer/time.rb b/activesupport/lib/active_support/core_ext/integer/time.rb new file mode 100644 index 0000000000..356e145b90 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/integer/time.rb @@ -0,0 +1,45 @@ +module ActiveSupport #:nodoc: + module CoreExtensions #:nodoc: + module Integer #:nodoc: + # Enables the use of time calculations and declarations, like 45.minutes + 2.hours + 4.years. + # + # These methods use Time#advance for precise date calculations when using from_now, ago, etc. + # as well as adding or subtracting their results from a Time object. For example: + # + # # equivalent to Time.now.advance(:months => 1) + # 1.month.from_now + # + # # equivalent to Time.now.advance(:years => 2) + # 2.years.from_now + # + # # equivalent to Time.now.advance(:months => 4, :years => 5) + # (4.months + 5.years).from_now + # + # While these methods provide precise calculation when used as in the examples above, care + # should be taken to note that this is not true if the result of `months', `years', etc is + # converted before use: + # + # # equivalent to 30.days.to_i.from_now + # 1.month.to_i.from_now + # + # # equivalent to 365.25.days.to_f.from_now + # 1.year.to_f.from_now + # + # In such cases, Ruby's core + # Date[http://stdlib.rubyonrails.org/libdoc/date/rdoc/index.html] and + # Time[http://stdlib.rubyonrails.org/libdoc/time/rdoc/index.html] should be used for precision + # date and time arithmetic + module Time + def months + ActiveSupport::Duration.new(self * 30.days, [[:months, self]]) + end + alias :month :months + + def years + ActiveSupport::Duration.new(self * 365.25.days, [[:years, self]]) + end + alias :year :years + end + end + end +end diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb index aac698f7a8..bc7f180cd1 100644 --- a/activesupport/lib/active_support/core_ext/numeric/time.rb +++ b/activesupport/lib/active_support/core_ext/numeric/time.rb @@ -60,16 +60,6 @@ module ActiveSupport #:nodoc: end alias :fortnight :fortnights - def months - ActiveSupport::Duration.new(self * 30.days, [[:months, self]]) - end - alias :month :months - - def years - ActiveSupport::Duration.new(self * 365.25.days, [[:years, self]]) - end - alias :year :years - # Reads best without arguments: 10.minutes.ago def ago(time = ::Time.now) time - self diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 3cc6d59907..00078de692 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -98,6 +98,16 @@ module ActiveSupport #:nodoc: # <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>, # <tt>:minutes</tt>, <tt>:seconds</tt>. def advance(options) + unless options[:weeks].nil? + options[:weeks], partial_weeks = options[:weeks].divmod(1) + options[:days] = (options[:days] || 0) + 7 * partial_weeks + end + + unless options[:days].nil? + options[:days], partial_days = options[:days].divmod(1) + options[:hours] = (options[:hours] || 0) + 24 * partial_days + end + d = to_date.advance(options) time_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day) seconds_to_advance = (options[:seconds] || 0) + (options[:minutes] || 0) * 60 + (options[:hours] || 0) * 3600 diff --git a/activesupport/lib/active_support/inflector.rb b/activesupport/lib/active_support/inflector.rb index 89a93f4a5f..1ccfec4000 100644 --- a/activesupport/lib/active_support/inflector.rb +++ b/activesupport/lib/active_support/inflector.rb @@ -1,4 +1,5 @@ require 'singleton' +require 'iconv' module ActiveSupport # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without, @@ -258,12 +259,31 @@ module ActiveSupport # # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a> def parameterize(string, sep = '-') re_sep = Regexp.escape(sep) - string.mb_chars.normalize(:kd). # Decompose accented characters - gsub(/[^\x00-\x7F]+/, ''). # Remove anything non-ASCII entirely (e.g. diacritics). - gsub(/[^a-z0-9\-_\+]+/i, sep). # Turn unwanted chars into the separator. - squeeze(sep). # No more than one of the separator in a row. - gsub(/^#{re_sep}|#{re_sep}$/i, ''). # Remove leading/trailing separator. - downcase + # replace accented chars with ther ascii equivalents + parameterized_string = transliterate(string) + # Turn unwanted chars into the seperator + parameterized_string.gsub!(/[^a-z0-9\-_\+]+/i, sep) + # No more than one of the separator in a row. + parameterized_string.squeeze!(sep) + # Remove leading/trailing separator. + parameterized_string.gsub!(/^#{re_sep}|#{re_sep}$/i, '') + parameterized_string.downcase + end + + + # Replaces accented characters with their ascii equivalents. + def transliterate(string) + Iconv.iconv('ascii//ignore//translit', 'utf-8', string).to_s + end + + # The iconv transliteration code doesn't function correctly + # on some platforms, but it's very fast where it does function. + if "foo" != Inflector.transliterate("föö") + undef_method :transliterate + def transliterate(string) + string.mb_chars.normalize(:kd). # Decompose accented characters + gsub(/[^\x00-\x7F]+/, '') # Remove anything non-ASCII entirely (e.g. diacritics). + end end # Create the name of a table like Rails does for models to table names. This method diff --git a/activesupport/lib/active_support/locale/en-US.yml b/activesupport/lib/active_support/locale/en-US.yml index 60ecb1d42a..c31694b9d6 100644 --- a/activesupport/lib/active_support/locale/en-US.yml +++ b/activesupport/lib/active_support/locale/en-US.yml @@ -29,3 +29,4 @@ en-US: support: array: sentence_connector: "and" + skip_last_comma: false diff --git a/activesupport/lib/active_support/memoizable.rb b/activesupport/lib/active_support/memoizable.rb index 4786fd6e0b..cd5c01cda2 100644 --- a/activesupport/lib/active_support/memoizable.rb +++ b/activesupport/lib/active_support/memoizable.rb @@ -1,5 +1,9 @@ module ActiveSupport module Memoizable + MEMOIZED_IVAR = Proc.new do |symbol| + "@_memoized_#{symbol.to_s.sub(/\?\Z/, '_query').sub(/!\Z/, '_bang')}".to_sym + end + module Freezable def self.included(base) base.class_eval do @@ -20,7 +24,7 @@ module ActiveSupport if method(m).arity == 0 __send__($1) else - ivar = :"@_memoized_#{$1}" + ivar = MEMOIZED_IVAR.call($1) instance_variable_set(ivar, {}) end end @@ -30,7 +34,7 @@ module ActiveSupport def unmemoize_all methods.each do |m| if m.to_s =~ /^_unmemoized_(.*)/ - ivar = :"@_memoized_#{$1}" + ivar = MEMOIZED_IVAR.call($1) instance_variable_get(ivar).clear if instance_variable_defined?(ivar) end end @@ -40,7 +44,7 @@ module ActiveSupport def memoize(*symbols) symbols.each do |symbol| original_method = :"_unmemoized_#{symbol}" - memoized_ivar = :"@_memoized_#{symbol.to_s.sub(/\?\Z/, '_query').sub(/!\Z/, '_bang')}" + memoized_ivar = MEMOIZED_IVAR.call(symbol) class_eval <<-EOS, __FILE__, __LINE__ include Freezable diff --git a/activesupport/lib/active_support/multibyte.rb b/activesupport/lib/active_support/multibyte.rb index 018aafe607..65a96af49a 100644 --- a/activesupport/lib/active_support/multibyte.rb +++ b/activesupport/lib/active_support/multibyte.rb @@ -8,13 +8,13 @@ module ActiveSupport #:nodoc: module Multibyte # A list of all available normalization forms. See http://www.unicode.org/reports/tr15/tr15-29.html for more # information about normalization. - NORMALIZATIONS_FORMS = [:c, :kc, :d, :kd] + NORMALIZATION_FORMS = [:c, :kc, :d, :kd] # The Unicode version that is supported by the implementation UNICODE_VERSION = '5.1.0' # The default normalization used for operations that require normalization. It can be set to any of the - # normalizations in NORMALIZATIONS_FORMS. + # normalizations in NORMALIZATION_FORMS. # # Example: # ActiveSupport::Multibyte.default_normalization_form = :c diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb index 2dbaf8a405..30e3655b7b 100644 --- a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb +++ b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb @@ -30,7 +30,13 @@ module I18n options.delete(:default) values = options.reject{|name, value| reserved.include? name } - entry = lookup(locale, key, scope) || default(locale, default, options) || raise(I18n::MissingTranslationData.new(locale, key, options)) + entry = lookup(locale, key, scope) + if entry.nil? + entry = default(locale, default, options) + if entry.nil? + raise(I18n::MissingTranslationData.new(locale, key, options)) + end + end entry = pluralize locale, entry, count entry = interpolate locale, entry, values entry @@ -83,7 +89,13 @@ module I18n return unless key init_translations unless initialized? keys = I18n.send :normalize_translation_keys, locale, key, scope - keys.inject(translations){|result, k| result[k.to_sym] or return nil } + keys.inject(translations) do |result, k| + if (x = result[k.to_sym]).nil? + return nil + else + x + end + end end # Evaluates a default translation. diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index f802ed8760..80cc6d25d3 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -30,7 +30,57 @@ class DurationTest < Test::Unit::TestCase end end + def test_fractional_weeks + assert_equal (86400 * 7) * 1.5, 1.5.weeks + assert_equal (86400 * 7) * 1.7, 1.7.weeks + end + + def test_fractional_days + assert_equal 86400 * 1.5, 1.5.days + assert_equal 86400 * 1.7, 1.7.days + end + uses_mocha 'TestDurationSinceAndAgoWithCurrentTime' do + def test_since_and_ago_with_fractional_days + Time.stubs(:now).returns Time.local(2000) + # since + assert_equal 36.hours.since, 1.5.days.since + assert_equal((24 * 1.7).hours.since, 1.7.days.since) + # ago + assert_equal 36.hours.ago, 1.5.days.ago + assert_equal((24 * 1.7).hours.ago, 1.7.days.ago) + end + + def test_since_and_ago_with_fractional_weeks + Time.stubs(:now).returns Time.local(2000) + # since + assert_equal((7 * 36).hours.since, 1.5.weeks.since) + assert_equal((7 * 24 * 1.7).hours.since, 1.7.weeks.since) + # ago + assert_equal((7 * 36).hours.ago, 1.5.weeks.ago) + assert_equal((7 * 24 * 1.7).hours.ago, 1.7.weeks.ago) + end + + def test_deprecated_fractional_years + years_re = /Fractional years are not respected\. Convert value to integer before calling #years\./ + assert_deprecated(years_re){1.0.years} + assert_deprecated(years_re){1.5.years} + assert_not_deprecated{1.years} + assert_deprecated(years_re){1.0.year} + assert_deprecated(years_re){1.5.year} + assert_not_deprecated{1.year} + end + + def test_deprecated_fractional_months + months_re = /Fractional months are not respected\. Convert value to integer before calling #months\./ + assert_deprecated(months_re){1.5.months} + assert_deprecated(months_re){1.0.months} + assert_not_deprecated{1.months} + assert_deprecated(months_re){1.5.month} + assert_deprecated(months_re){1.0.month} + assert_not_deprecated{1.month} + end + def test_since_and_ago_anchored_to_time_now_when_time_zone_default_not_set Time.zone_default = nil with_env_tz 'US/Eastern' do diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 8ceaedc7f4..11ec1c6cd6 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -381,7 +381,11 @@ class TimeExtCalculationsTest < Test::Unit::TestCase assert_equal Time.local(2006,2,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => 1) assert_equal Time.local(2005,6,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(:months => 4) assert_equal Time.local(2005,3,21,15,15,10), Time.local(2005,2,28,15,15,10).advance(:weeks => 3) + assert_equal Time.local(2005,3,25,3,15,10), Time.local(2005,2,28,15,15,10).advance(:weeks => 3.5) + assert_equal Time.local(2005,3,26,12,51,10), Time.local(2005,2,28,15,15,10).advance(:weeks => 3.7) assert_equal Time.local(2005,3,5,15,15,10), Time.local(2005,2,28,15,15,10).advance(:days => 5) + assert_equal Time.local(2005,3,6,3,15,10), Time.local(2005,2,28,15,15,10).advance(:days => 5.5) + assert_equal Time.local(2005,3,6,8,3,10), Time.local(2005,2,28,15,15,10).advance(:days => 5.7) assert_equal Time.local(2012,9,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => 7, :months => 7) assert_equal Time.local(2013,10,3,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :days => 5) assert_equal Time.local(2013,10,17,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5) @@ -399,7 +403,11 @@ class TimeExtCalculationsTest < Test::Unit::TestCase assert_equal Time.utc(2006,2,22,15,15,10), Time.utc(2005,2,22,15,15,10).advance(:years => 1) assert_equal Time.utc(2005,6,22,15,15,10), Time.utc(2005,2,22,15,15,10).advance(:months => 4) assert_equal Time.utc(2005,3,21,15,15,10), Time.utc(2005,2,28,15,15,10).advance(:weeks => 3) + assert_equal Time.utc(2005,3,25,3,15,10), Time.utc(2005,2,28,15,15,10).advance(:weeks => 3.5) + assert_equal Time.utc(2005,3,26,12,51,10), Time.utc(2005,2,28,15,15,10).advance(:weeks => 3.7) assert_equal Time.utc(2005,3,5,15,15,10), Time.utc(2005,2,28,15,15,10).advance(:days => 5) + assert_equal Time.utc(2005,3,6,3,15,10), Time.utc(2005,2,28,15,15,10).advance(:days => 5.5) + assert_equal Time.utc(2005,3,6,8,3,10), Time.utc(2005,2,28,15,15,10).advance(:days => 5.7) assert_equal Time.utc(2012,9,22,15,15,10), Time.utc(2005,2,22,15,15,10).advance(:years => 7, :months => 7) assert_equal Time.utc(2013,10,3,15,15,10), Time.utc(2005,2,22,15,15,10).advance(:years => 7, :months => 19, :days => 11) assert_equal Time.utc(2013,10,17,15,15,10), Time.utc(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5) diff --git a/activesupport/test/i18n_test.rb b/activesupport/test/i18n_test.rb index 4b17e3c523..db5bd5e088 100644 --- a/activesupport/test/i18n_test.rb +++ b/activesupport/test/i18n_test.rb @@ -72,4 +72,20 @@ class I18nTest < Test::Unit::TestCase def test_time_pm assert_equal 'pm', I18n.translate(:'time.pm') end + + def test_sentence_connector + assert_equal 'and', I18n.translate(:'support.array.sentence_connector') + end + + def test_skip_last_comma + assert_equal false, I18n.translate(:'support.array.skip_last_comma') + end + + def test_to_sentence + assert_equal 'a, b, and c', %w[a b c].to_sentence + I18n.backend.store_translations 'en-US', :support => { :array => { :skip_last_comma => true } } + assert_equal 'a, b and c', %w[a b c].to_sentence + ensure + I18n.backend.store_translations 'en-US', :support => { :array => { :skip_last_comma => false } } + end end diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index fc7a35f859..3aa18ca781 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -1,3 +1,5 @@ +# encoding: utf-8 + module InflectorTestCases SingularToPlural = { "search" => "searches", diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index 497f028369..c070e0d9ed 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -100,6 +100,18 @@ class TestJSONEncoding < Test::Unit::TestCase ActiveSupport.use_standard_json_time_format = false end + def test_nested_hash_with_float + assert_nothing_raised do + hash = { + "CHI" => { + :dislay_name => "chicago", + :latitude => 123.234 + } + } + result = hash.to_json + end + end + protected def object_keys(json_object) diff --git a/activesupport/test/memoizable_test.rb b/activesupport/test/memoizable_test.rb index 135d56f14a..a78ebd9425 100644 --- a/activesupport/test/memoizable_test.rb +++ b/activesupport/test/memoizable_test.rb @@ -100,6 +100,11 @@ uses_mocha 'Memoizable' do def test_memoization_with_punctuation assert_equal true, @person.name? + + assert_nothing_raised(NameError) do + @person.memoize_all + @person.unmemoize_all + end end def test_memoization_with_nil_value diff --git a/railties/doc/guides/securing_rails_applications/security.txt b/railties/doc/guides/securing_rails_applications/security.txt index 6e520cb2ce..aa1fcf4171 100644 --- a/railties/doc/guides/securing_rails_applications/security.txt +++ b/railties/doc/guides/securing_rails_applications/security.txt @@ -859,4 +859,4 @@ The security landscape shifts and it is important to keep up to date, because mi - Subscribe to the Rails security http://groups.google.com/group/rubyonrails-security[mailing list] - http://secunia.com/[Keep up to date on the other application layers] (they have a weekly newsletter, too) - A http://ha.ckers.org/blog/[good security blog] including the http://ha.ckers.org/xss.html[Cross-Site scripting Cheat Sheet] -- Another http://www.0x000000.com/[good security blog] with some Cheat Sheets, too
\ No newline at end of file +- Another http://www.0x000000.com/[good security blog] with some Cheat Sheets, too diff --git a/railties/environments/environment.rb b/railties/environments/environment.rb index 09471f16bc..f27ccc6bb8 100644 --- a/railties/environments/environment.rb +++ b/railties/environments/environment.rb @@ -22,7 +22,7 @@ Rails::Initializer.run do |config| # Specify gems that this application depends on. # They can then be installed with "rake gems:install" on new installations. - # You HAVE to specify the <tt>:lib</tt> option for libraries, where the Gem name (<em>sqlite3-ruby</em>) differs from the file itself (_sqlite3_) + # You have to specify the <tt>:lib</tt> option for libraries, where the Gem name (<em>sqlite3-ruby</em>) differs from the file itself (_sqlite3_) # config.gem "bj" # config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net" # config.gem "sqlite3-ruby", :lib => "sqlite3" diff --git a/railties/lib/console_sandbox.rb b/railties/lib/console_sandbox.rb index 5d57679c43..65a3d68619 100644 --- a/railties/lib/console_sandbox.rb +++ b/railties/lib/console_sandbox.rb @@ -1,6 +1,6 @@ -ActiveRecord::Base.send :increment_open_transactions +ActiveRecord::Base.connection.increment_open_transactions ActiveRecord::Base.connection.begin_db_transaction at_exit do ActiveRecord::Base.connection.rollback_db_transaction - ActiveRecord::Base.send :decrement_open_transactions + ActiveRecord::Base.connection.decrement_open_transactions end diff --git a/railties/lib/rails/gem_dependency.rb b/railties/lib/rails/gem_dependency.rb index 3b2f4846a6..80481859e1 100644 --- a/railties/lib/rails/gem_dependency.rb +++ b/railties/lib/rails/gem_dependency.rb @@ -82,6 +82,10 @@ module Rails File.join(base_directory, specification.full_name) end + def spec_filename(base_directory) + File.join(gem_dir(base_directory), '.specification') + end + def load return if @loaded || @load_paths_added == false require(@lib || name) unless @lib == false @@ -108,6 +112,8 @@ module Rails @loaded ||= begin if vendor_rails? true + elsif specification.nil? + false else # check if the gem is loaded by inspecting $" # specification.files lists all the files contained in the gem @@ -144,17 +150,42 @@ module Rails Gem::GemRunner.new.run(unpack_command) end + # Gem.activate changes the spec - get the original + real_spec = Gem::Specification.load(spec.loaded_from) + write_spec(directory, real_spec) + + end + + def write_spec(directory, spec) # copy the gem's specification into GEMDIR/.specification so that # we can access information about the gem on deployment systems # without having the gem installed - spec_filename = File.join(gem_dir(directory), '.specification') - # Gem.activate changes the spec - get the original - spec = Gem::Specification.load(specification.loaded_from) - File.open(spec_filename, 'w') do |file| + File.open(spec_filename(directory), 'w') do |file| file.puts spec.to_yaml end end + def refresh_spec(directory) + real_gems = Gem.source_index.installed_source_index + exact_dep = Gem::Dependency.new(name, "= #{specification.version}") + matches = real_gems.search(exact_dep) + installed_spec = matches.first + if installed_spec + # we have a real copy + # get a fresh spec - matches should only have one element + # note that there is no reliable method to check that the loaded + # spec is the same as the copy from real_gems - Gem.activate changes + # some of the fields + real_spec = Gem::Specification.load(matches.first.loaded_from) + write_spec(directory, real_spec) + puts "Reloaded specification for #{name} from installed gems." + else + # the gem isn't installed locally - write out our current specs + write_spec(directory, specification) + puts "Gem #{name} not loaded locally - writing out current spec." + end + end + def ==(other) self.name == other.name && self.requirement == other.requirement end diff --git a/railties/lib/rails/mongrel_server/commands.rb b/railties/lib/rails/mongrel_server/commands.rb index 0a92f418ad..d29b18712f 100644 --- a/railties/lib/rails/mongrel_server/commands.rb +++ b/railties/lib/rails/mongrel_server/commands.rb @@ -44,7 +44,7 @@ module Rails env_location = "#{defaults[:cwd]}/config/environment" require env_location - ActionController::AbstractRequest.relative_url_root = defaults[:prefix] + ActionController::Base.relative_url_root = defaults[:prefix] uri prefix, :handler => Rails::MongrelServer::RailsHandler.new end end diff --git a/railties/lib/rails/vendor_gem_source_index.rb b/railties/lib/rails/vendor_gem_source_index.rb index c8701101e2..dc821693ac 100644 --- a/railties/lib/rails/vendor_gem_source_index.rb +++ b/railties/lib/rails/vendor_gem_source_index.rb @@ -13,6 +13,16 @@ module Rails attr_reader :installed_source_index attr_reader :vendor_source_index + @@silence_spec_warnings = false + + def self.silence_spec_warnings + @@silence_spec_warnings + end + + def self.silence_spec_warnings=(v) + @@silence_spec_warnings = v + end + def initialize(installed_index, vendor_dir=Rails::GemDependency.unpacked_path) @installed_source_index = installed_index @vendor_dir = vendor_dir @@ -33,38 +43,75 @@ module Rails # load specifications from vendor/gems Dir[File.join(Rails::GemDependency.unpacked_path, '*')].each do |d| + dir_name = File.basename(d) + dir_version = version_for_dir(dir_name) spec = load_specification(d) - next unless spec - # NOTE: this is a bit of a hack - the gem system expects a different structure - # than we have. - # It's looking for: - # repository - # -> specifications - # - gem_name.spec <= loaded_from points to this - # -> gems - # - gem_name <= gem files here - # and therefore goes up one directory from loaded_from, then adds gems/gem_name - # to the path. - # But we have: - # vendor - # -> gems - # -> gem_name <= gem files here - # - .specification - # so we set loaded_from to vendor/gems/.specification (not a real file) to - # get the correct behavior. - spec.loaded_from = File.join(Rails::GemDependency.unpacked_path, '.specification') + if spec + if spec.full_name != dir_name + # mismatched directory name and gem spec - produced by 2.1.0-era unpack code + if dir_version + # fix the spec version - this is not optimal (spec.files may be wrong) + # but it's better than breaking apps. Complain to remind users to get correct specs. + # use ActiveSupport::Deprecation.warn, as the logger is not set yet + $stderr.puts("config.gem: Unpacked gem #{dir_name} in vendor/gems has a mismatched specification file."+ + " Run 'rake gems:refresh_specs' to fix this.") unless @@silence_spec_warnings + spec.version = dir_version + else + $stderr.puts("config.gem: Unpacked gem #{dir_name} in vendor/gems is not in a versioned directory"+ + "(should be #{spec.full_name}).") unless @@silence_spec_warnings + # continue, assume everything is OK + end + end + else + # no spec - produced by early-2008 unpack code + # emulate old behavior, and complain. + $stderr.puts("config.gem: Unpacked gem #{dir_name} in vendor/gems has no specification file."+ + " Run 'rake gems:refresh_specs' to fix this.") unless @@silence_spec_warnings + if dir_version + spec = Gem::Specification.new + spec.version = dir_version + spec.require_paths = ['lib'] + ext_path = File.join(d, 'ext') + spec.require_paths << 'ext' if File.exist?(ext_path) + spec.name = /^(.*)-[^-]+$/.match(dir_name)[1] + files = ['lib'] + # set files to everything in lib/ + files += Dir[File.join(d, 'lib', '*')].map { |v| v.gsub(/^#{d}\//, '') } + files += Dir[File.join(d, 'ext', '*')].map { |v| v.gsub(/^#{d}\//, '') } if ext_path + spec.files = files + else + $stderr.puts("config.gem: Unpacked gem #{dir_name} in vendor/gems not in a versioned directory."+ + " Giving up.") unless @silence_spec_warnings + next + end + end + spec.loaded_from = File.join(d, '.specification') + # finally, swap out full_gem_path + # it would be better to use a Gem::Specification subclass, but the YAML loads an explicit class + class << spec + def full_gem_path + path = File.join installation_path, full_name + return path if File.directory? path + File.join installation_path, original_name + end + end vendor_gems[File.basename(d)] = spec end @vendor_source_index = Gem::SourceIndex.new(vendor_gems) end + def version_for_dir(d) + matches = /-([^-]+)$/.match(d) + Gem::Version.new(matches[1]) if matches + end + def load_specification(gem_dir) spec_file = File.join(gem_dir, '.specification') YAML.load_file(spec_file) if File.exist?(spec_file) end - def find_name(gem_name, version_requirement = Gem::Requirement.default) - search(/^#{gem_name}$/, version_requirement) + def find_name(*args) + @installed_source_index.find_name(*args) + @vendor_source_index.find_name(*args) end def search(*args) diff --git a/railties/lib/rails_generator/commands.rb b/railties/lib/rails_generator/commands.rb index 59af7308fe..6b9a636847 100644 --- a/railties/lib/rails_generator/commands.rb +++ b/railties/lib/rails_generator/commands.rb @@ -169,6 +169,7 @@ HELP # Ruby or Rails. In the future, expand to check other namespaces # such as the rest of the user's app. def class_collisions(*class_names) + path = class_names.shift class_names.flatten.each do |class_name| # Convert to string to allow symbol arguments. class_name = class_name.to_s diff --git a/railties/lib/rails_generator/generators/components/controller/controller_generator.rb b/railties/lib/rails_generator/generators/components/controller/controller_generator.rb index c37ff45836..77b2220d57 100644 --- a/railties/lib/rails_generator/generators/components/controller/controller_generator.rb +++ b/railties/lib/rails_generator/generators/components/controller/controller_generator.rb @@ -2,7 +2,7 @@ class ControllerGenerator < Rails::Generator::NamedBase def manifest record do |m| # Check for class naming collisions. - m.class_collisions class_path, "#{class_name}Controller", "#{class_name}ControllerTest", "#{class_name}Helper" + m.class_collisions "#{class_name}Controller", "#{class_name}ControllerTest", "#{class_name}Helper" # Controller, helper, views, and test directories. m.directory File.join('app/controllers', class_path) diff --git a/railties/lib/rails_generator/generators/components/integration_test/integration_test_generator.rb b/railties/lib/rails_generator/generators/components/integration_test/integration_test_generator.rb index 90fa96938b..44323f28ca 100644 --- a/railties/lib/rails_generator/generators/components/integration_test/integration_test_generator.rb +++ b/railties/lib/rails_generator/generators/components/integration_test/integration_test_generator.rb @@ -4,7 +4,7 @@ class IntegrationTestGenerator < Rails::Generator::NamedBase def manifest record do |m| # Check for class naming collisions. - m.class_collisions class_path, class_name, "#{class_name}Test" + m.class_collisions class_name, "#{class_name}Test" # integration test directory m.directory File.join('test/integration', class_path) diff --git a/railties/lib/rails_generator/generators/components/mailer/mailer_generator.rb b/railties/lib/rails_generator/generators/components/mailer/mailer_generator.rb index dc1202d7c8..ba6d60cac6 100644 --- a/railties/lib/rails_generator/generators/components/mailer/mailer_generator.rb +++ b/railties/lib/rails_generator/generators/components/mailer/mailer_generator.rb @@ -2,7 +2,7 @@ class MailerGenerator < Rails::Generator::NamedBase def manifest record do |m| # Check for class naming collisions. - m.class_collisions class_path, class_name, "#{class_name}Test" + m.class_collisions class_name, "#{class_name}Test" # Mailer, view, test, and fixture directories. m.directory File.join('app/models', class_path) diff --git a/railties/lib/rails_generator/generators/components/model/model_generator.rb b/railties/lib/rails_generator/generators/components/model/model_generator.rb index 9be9cad8bf..582a28922f 100644 --- a/railties/lib/rails_generator/generators/components/model/model_generator.rb +++ b/railties/lib/rails_generator/generators/components/model/model_generator.rb @@ -4,7 +4,7 @@ class ModelGenerator < Rails::Generator::NamedBase def manifest record do |m| # Check for class naming collisions. - m.class_collisions class_path, class_name, "#{class_name}Test" + m.class_collisions class_name, "#{class_name}Test" # Model, test, and fixture directories. m.directory File.join('app/models', class_path) diff --git a/railties/lib/rails_generator/generators/components/observer/observer_generator.rb b/railties/lib/rails_generator/generators/components/observer/observer_generator.rb index 18fbd32b75..3c4b330a80 100644 --- a/railties/lib/rails_generator/generators/components/observer/observer_generator.rb +++ b/railties/lib/rails_generator/generators/components/observer/observer_generator.rb @@ -2,7 +2,7 @@ class ObserverGenerator < Rails::Generator::NamedBase def manifest record do |m| # Check for class naming collisions. - m.class_collisions class_path, "#{class_name}Observer", "#{class_name}ObserverTest" + m.class_collisions "#{class_name}Observer", "#{class_name}ObserverTest" # Observer, and test directories. m.directory File.join('app/models', class_path) diff --git a/railties/lib/rails_generator/generators/components/performance_test/performance_test_generator.rb b/railties/lib/rails_generator/generators/components/performance_test/performance_test_generator.rb index fbcc1cf683..83ce8ac674 100644 --- a/railties/lib/rails_generator/generators/components/performance_test/performance_test_generator.rb +++ b/railties/lib/rails_generator/generators/components/performance_test/performance_test_generator.rb @@ -4,7 +4,7 @@ class PerformanceTestGenerator < Rails::Generator::NamedBase def manifest record do |m| # Check for class naming collisions. - m.class_collisions class_path, class_name, "#{class_name}Test" + m.class_collisions class_name, "#{class_name}Test" # performance test directory m.directory File.join('test/performance', class_path) diff --git a/railties/lib/rails_generator/generators/components/plugin/plugin_generator.rb b/railties/lib/rails_generator/generators/components/plugin/plugin_generator.rb index 7a619825a2..615c575e6e 100644 --- a/railties/lib/rails_generator/generators/components/plugin/plugin_generator.rb +++ b/railties/lib/rails_generator/generators/components/plugin/plugin_generator.rb @@ -10,7 +10,7 @@ class PluginGenerator < Rails::Generator::NamedBase def manifest record do |m| # Check for class naming collisions. - m.class_collisions class_path, class_name + m.class_collisions class_name m.directory "#{plugin_path}/lib" m.directory "#{plugin_path}/tasks" diff --git a/railties/lib/rails_generator/generators/components/resource/resource_generator.rb b/railties/lib/rails_generator/generators/components/resource/resource_generator.rb index d5491ece36..ea6dd65bde 100644 --- a/railties/lib/rails_generator/generators/components/resource/resource_generator.rb +++ b/railties/lib/rails_generator/generators/components/resource/resource_generator.rb @@ -30,8 +30,8 @@ class ResourceGenerator < Rails::Generator::NamedBase def manifest record do |m| # Check for class naming collisions. - m.class_collisions(controller_class_path, "#{controller_class_name}Controller", "#{controller_class_name}Helper") - m.class_collisions(class_path, "#{class_name}") + m.class_collisions("#{controller_class_name}Controller", "#{controller_class_name}Helper") + m.class_collisions(class_name) # Controller, helper, views, and test directories. m.directory(File.join('app/models', class_path)) diff --git a/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb b/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb index 5fecfe0167..ff0381da2a 100644 --- a/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb +++ b/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb @@ -36,8 +36,8 @@ class ScaffoldGenerator < Rails::Generator::NamedBase def manifest record do |m| # Check for class naming collisions. - m.class_collisions(controller_class_path, "#{controller_class_name}Controller", "#{controller_class_name}Helper") - m.class_collisions(class_path, "#{class_name}") + m.class_collisions("#{controller_class_name}Controller", "#{controller_class_name}Helper") + m.class_collisions(class_name) # Controller, helper, views, test and stylesheets directories. m.directory(File.join('app/models', class_path)) diff --git a/railties/lib/tasks/gems.rake b/railties/lib/tasks/gems.rake index 9abdfc56b6..c65dfc7dd4 100644 --- a/railties/lib/tasks/gems.rake +++ b/railties/lib/tasks/gems.rake @@ -65,4 +65,14 @@ namespace :gems do end end end + + desc "Regenerate gem specifications in correct format." + task :refresh_specs => :base do + require 'rubygems' + require 'rubygems/gem_runner' + Rails.configuration.gems.each do |gem| + next unless gem.frozen? && (ENV['GEM'].blank? || ENV['GEM'] == gem.name) + gem.refresh_spec(Rails::GemDependency.unpacked_path) if gem.loaded? + end + end end
\ No newline at end of file diff --git a/railties/test/gem_dependency_test.rb b/railties/test/gem_dependency_test.rb index 89e25341d1..5f026b2616 100644 --- a/railties/test/gem_dependency_test.rb +++ b/railties/test/gem_dependency_test.rb @@ -1,3 +1,6 @@ +require 'lib/rails/vendor_gem_source_index' +Rails::VendorGemSourceIndex.silence_spec_warnings = true + require 'plugin_test_helper' class Rails::GemDependency @@ -110,5 +113,22 @@ uses_mocha "Plugin Tests" do assert_equal '0.6.0', DUMMY_GEM_C_VERSION end + def test_gem_load_missing_specification + dummy_gem = Rails::GemDependency.new "dummy-gem-d" + dummy_gem.add_load_paths + dummy_gem.load + assert_not_nil DUMMY_GEM_D_VERSION + assert_equal '1.0.0', DUMMY_GEM_D_VERSION + assert_equal ['lib', 'lib/dummy-gem-d.rb'], dummy_gem.specification.files + end + + def test_gem_load_bad_specification + dummy_gem = Rails::GemDependency.new "dummy-gem-e", :version => "= 1.0.0" + dummy_gem.add_load_paths + dummy_gem.load + assert_not_nil DUMMY_GEM_E_VERSION + assert_equal '1.0.0', DUMMY_GEM_E_VERSION + end + end end diff --git a/railties/test/generators/rails_controller_generator_test.rb b/railties/test/generators/rails_controller_generator_test.rb index 8304fb5a01..f839ea97e6 100644 --- a/railties/test/generators/rails_controller_generator_test.rb +++ b/railties/test/generators/rails_controller_generator_test.rb @@ -1,5 +1,8 @@ require 'generators/generator_test_helper' +module Admin +end + class RailsControllerGeneratorTest < GeneratorTestCase def test_controller_generates_controller diff --git a/railties/test/vendor/gems/dummy-gem-d-1.0.0/lib/dummy-gem-d.rb b/railties/test/vendor/gems/dummy-gem-d-1.0.0/lib/dummy-gem-d.rb new file mode 100644 index 0000000000..e5cb007e5f --- /dev/null +++ b/railties/test/vendor/gems/dummy-gem-d-1.0.0/lib/dummy-gem-d.rb @@ -0,0 +1 @@ +DUMMY_GEM_D_VERSION="1.0.0" diff --git a/railties/test/vendor/gems/dummy-gem-e-1.0.0/.specification b/railties/test/vendor/gems/dummy-gem-e-1.0.0/.specification new file mode 100644 index 0000000000..ce4443c8be --- /dev/null +++ b/railties/test/vendor/gems/dummy-gem-e-1.0.0/.specification @@ -0,0 +1,28 @@ +--- !ruby/object:Gem::Specification +name: dummy-gem-e +version: !ruby/object:Gem::Version + version: 1.3.0 +platform: ruby +authors: +- "Nobody" +date: 2008-10-03 00:00:00 -04:00 +files: +- lib +- lib/dummy-gem-e.rb +require_paths: +- lib +required_ruby_version: !ruby/object:Gem::Requirement + requirements: + - - ">=" + - !ruby/object:Gem::Version + version: "0" + version: +required_rubygems_version: !ruby/object:Gem::Requirement + requirements: + - - ">=" + - !ruby/object:Gem::Version + version: "0" + version: +requirements: [] +specification_version: 2 +summary: Dummy Gem E diff --git a/railties/test/vendor/gems/dummy-gem-e-1.0.0/lib/dummy-gem-e.rb b/railties/test/vendor/gems/dummy-gem-e-1.0.0/lib/dummy-gem-e.rb new file mode 100644 index 0000000000..48bf91a701 --- /dev/null +++ b/railties/test/vendor/gems/dummy-gem-e-1.0.0/lib/dummy-gem-e.rb @@ -0,0 +1 @@ +DUMMY_GEM_E_VERSION="1.0.0" |