diff options
24 files changed, 767 insertions, 203 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index f692b169df..184b2af018 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -8,7 +8,9 @@ * Implicit actions named not_implemented can be rendered [Santiago Pastorino] -* Wildcard route will always matching the optional format segment by default. For example if you have this route: +* Wildcard route will always matching the optional format segment by default. [Prem Sichanugrist] + + For example if you have this route: map '*pages' => 'pages#show' diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 0d667a76a7..a2570587ce 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -25,6 +25,7 @@ Gem::Specification.new do |s| s.add_dependency('rack', '~> 1.2.1') s.add_dependency('rack-test', '~> 0.5.7') s.add_dependency('rack-mount', '~> 0.7.2') + s.add_dependency('sprockets', '~> 2.0.0.beta.1') s.add_dependency('tzinfo', '~> 0.3.23') s.add_dependency('erubis', '~> 2.7.0') end diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 0dd847f967..1d27c3aa51 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -2,17 +2,16 @@ require 'active_support/core_ext/file/path' require 'rack/chunked' module ActionController #:nodoc: - # Allow views to be streamed back to the client as they are rendered. + # Allows views to be streamed back to the client as they are rendered. # # The default way Rails renders views is by first rendering the template - # and then the layout. The first chunk of response is sent to the client - # just after the whole template is rendered, all queries are made and the - # layout is processed. + # and then the layout. The response is sent to the client after the whole + # template is rendered, all queries are made, and the layout is processed. # # Streaming inverts the rendering flow by rendering the layout first and # streaming each part of the layout as they are processed. This allows the - # header of the html (which is usually in the layout) to be streamed back - # to client very quickly, allowing javascripts and stylesheets to be loaded + # header of the HTML (which is usually in the layout) to be streamed back + # to client very quickly, allowing JavaScripts and stylesheets to be loaded # earlier than usual. # # This approach was introduced in Rails 3.1 and is still improving. Several @@ -20,13 +19,13 @@ module ActionController #:nodoc: # Those points are going to be addressed soon. # # In order to use streaming, you will need to use a Ruby version that - # supports Fibers (Fibers are supported since version 1.9.2 of the main + # supports fibers (fibers are supported since version 1.9.2 of the main # Ruby implementation). # # == Examples # # Streaming can be added to a controller easily, all you need to do is - # call stream at the controller class: + # call +stream+ in the controller class: # # class PostsController # stream @@ -42,19 +41,19 @@ module ActionController #:nodoc: # # class PostsController # def index - # @post = Post.scoped + # @posts = Post.scoped # render :stream => true # end # end # # == When to use streaming # - # Streaming may be considering an overkill for common actions like - # new or edit. The real benefit of streaming is on expensive actions - # that, for example, does a lot of queries on the database. + # Streaming may be considered to be overkill for lightweight actions like + # +new+ or +edit+. The real benefit of streaming is on expensive actions + # that, for example, do a lot of queries on the database. # # In such actions, you want to delay queries execution as much as you can. - # For example, imagine the following dashboard action: + # For example, imagine the following +dashboard+ action: # # def dashboard # @posts = Post.all @@ -63,10 +62,10 @@ module ActionController #:nodoc: # end # # Most of the queries here are happening in the controller. In order to benefit - # most of streaming, you would want to rewrite it as: + # from streaming you would want to rewrite it as: # # def dashboard - # # Allow lazily execution of the query + # # Allow lazy execution of the queries # @posts = Post.scoped # @pages = Page.scoped # @articles = Article.scoped @@ -75,12 +74,15 @@ module ActionController #:nodoc: # # == Communication between layout and template # - # When streaming, the layout is rendered first than the template. - # This means that, if your application currently rely on variables set - # in the template to be used in the layout, they won't work once you - # move to streaming. The proper way to communicate between layout and - # template, regardless if you use streaming or not, is by using - # +content_for+, +provide+ and +yield+. + # When streaming, rendering happens top-down instead of inside-out. + # Rails starts with the layout, and the template is rendered later, + # when its +yield+ is reached. + # + # This means that, if your application currently relies on instance + # variables set in the template to be used in the layout, they won't + # work once you move to streaming. The proper way to communicate + # between layout and template, regardless of whether you use streaming + # or not, is by using +content_for+, +provide+ and +yield+. # # Take a simple example where the layout expects the template to tell # which title to use: @@ -121,16 +123,16 @@ module ActionController #:nodoc: # and you want to use streaming, you would have to render the whole template # (and eventually trigger all queries) before streaming the title and all # assets, which kills the purpose of streaming. For this reason Rails 3.1 - # introduces a helper called +provide+ that does the same as +content_for+ + # introduces a new helper called +provide+ that does the same as +content_for+ # but tells the layout to stop searching for other entries and continue rendering. # - # For instance, the template below, using +provide+: + # For instance, the template above using +provide+ would be: # # <%= provide :title, "Main" %> # Hello # <%= content_for :title, " page" %> # - # Has as final result: + # Giving: # # <html> # <head><title>Main</title></head> @@ -138,26 +140,26 @@ module ActionController #:nodoc: # </html> # # That said, when streaming, you need to properly check your templates - # and chose when to use +provide+ and +content_for+. + # and choose when to use +provide+ and +content_for+. # # == Headers, cookies, session and flash # # When streaming, the HTTP headers are sent to the client right before # it renders the first line. This means that, modifying headers, cookies, - # session or flash after the template start rendering will not propagate + # session or flash after the template starts rendering will not propagate # to the client. # - # If you try to modify cookies, session or flash, a ClosedError will be - # raised, showing those objects are closed for modification. + # If you try to modify cookies, session or flash, an +ActionDispatch::ClosedError+ + # will be raised, showing those objects are closed for modification. # # == Middlewares # # Middlewares that need to manipulate the body won't work with streaming. # You should disable those middlewares whenever streaming in development - # or production. For instance, Rack::Bug won't work when streaming as it + # or production. For instance, +Rack::Bug+ won't work when streaming as it # needs to inject contents in the HTML body. # - # Also Rack::Cache won't work with streaming as it does not support + # Also +Rack::Cache+ won't work with streaming as it does not support # streaming bodies yet. So, whenever streaming, Cache-Control is automatically # set to "no-cache". # @@ -193,10 +195,10 @@ module ActionController #:nodoc: # # unicorn_rails --config-file unicorn.config.rb # - # You may also want to configure other parameters like :tcp_nodelay. Please - # check its documentation for more information: http://unicorn.bogomips.org/Unicorn/Configurator.html#method-i-listen + # You may also want to configure other parameters like <tt>:tcp_nodelay</tt>. + # Please check its documentation for more information: http://unicorn.bogomips.org/Unicorn/Configurator.html#method-i-listen # - # If you are using unicorn with nginx, you may need to tweak nginx. + # If you are using Unicorn with Nginx, you may need to tweak Nginx. # Streaming should work out of the box on Rainbows. # # ==== Passenger diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 33a184d48d..6cdec8c487 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -93,20 +93,20 @@ module ActiveRecord first_or_last(:last, *args) end - def build(attributes = {}, &block) - build_or_create(attributes, :build, &block) + def build(attributes = {}, options = {}, &block) + build_or_create(:build, attributes, options, &block) end - def create(attributes = {}, &block) + def create(attributes = {}, options = {}, &block) unless owner.persisted? raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved" end - build_or_create(attributes, :create, &block) + build_or_create(:create, attributes, options, &block) end - def create!(attrs = {}, &block) - record = create(attrs, &block) + def create!(attrs = {}, options = {}, &block) + record = create(attrs, options, &block) Array.wrap(record).each(&:save!) record end @@ -403,9 +403,9 @@ module ActiveRecord end + existing end - def build_or_create(attributes, method) + def build_or_create(method, attributes, options) records = Array.wrap(attributes).map do |attrs| - record = build_record(attrs) + record = build_record(attrs, options) add_to_target(record) do yield(record) if block_given? @@ -421,8 +421,8 @@ module ActiveRecord raise NotImplementedError end - def build_record(attributes) - reflection.build_association(scoped.scope_for_create.merge(attributes)) + def build_record(attributes, options) + reflection.build_association(scoped.scope_for_create.merge(attributes), options) end def delete_or_destroy(records, method) diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index 9d2b29685b..7708228d23 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -60,10 +60,10 @@ module ActiveRecord through_record end - def build_record(attributes) + def build_record(attributes, options = {}) ensure_not_nested - record = super(attributes) + record = super(attributes, options) inverse = source_reflection.inverse_of if inverse diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index 4edbe216be..ea4d73d414 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -17,16 +17,16 @@ module ActiveRecord replace(record) end - def create(attributes = {}) - new_record(:create, attributes) + def create(attributes = {}, options = {}) + new_record(:create, attributes, options) end - def create!(attributes = {}) - build(attributes).tap { |record| record.save! } + def create!(attributes = {}, options = {}) + build(attributes, options).tap { |record| record.save! } end - def build(attributes = {}) - new_record(:build, attributes) + def build(attributes = {}, options = {}) + new_record(:build, attributes, options) end private @@ -44,9 +44,9 @@ module ActiveRecord replace(record) end - def new_record(method, attributes) + def new_record(method, attributes, options) attributes = scoped.scope_for_create.merge(attributes || {}) - record = reflection.send("#{method}_association", attributes) + record = reflection.send("#{method}_association", attributes, options) set_new_record(record) record end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 04c12f86b6..1919ceb158 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -475,10 +475,19 @@ module ActiveRecord #:nodoc: # The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the # attributes on the objects that are to be created. # + # +create+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options + # in the +options+ parameter. + # # ==== Examples # # Create a single new object # User.create(:first_name => 'Jamie') # + # # Create a single new object using the :admin mass-assignment security scope + # User.create({ :first_name => 'Jamie', :is_admin => true }, :as => :admin) + # + # # Create a single new object bypassing mass-assignment security + # User.create({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true) + # # # Create an Array of new objects # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) # @@ -491,11 +500,11 @@ module ActiveRecord #:nodoc: # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u| # u.is_admin = false # end - def create(attributes = nil, &block) + def create(attributes = nil, options = {}, &block) if attributes.is_a?(Array) - attributes.collect { |attr| create(attr, &block) } + attributes.collect { |attr| create(attr, options, &block) } else - object = new(attributes) + object = new(attributes, options) yield(object) if block_given? object.save object @@ -1180,19 +1189,15 @@ MSG # Use this macro in your model to set a default scope for all operations on # the model. # - # class Person < ActiveRecord::Base - # default_scope order('last_name, first_name') + # class Article < ActiveRecord::Base + # default_scope where(:published => true) # end # - # Person.all # => SELECT * FROM people ORDER BY last_name, first_name + # Article.all # => SELECT * FROM articles WHERE published = true # # The <tt>default_scope</tt> is also applied while creating/building a record. It is not # applied while updating a record. # - # class Article < ActiveRecord::Base - # default_scope where(:published => true) - # end - # # Article.new.published # => true # Article.create.published # => true # @@ -1205,6 +1210,19 @@ MSG # (You can also pass any object which responds to <tt>call</tt> to the <tt>default_scope</tt> # macro, and it will be called when building the default scope.) # + # If you use multiple <tt>default_scope</tt> declarations in your model then they will + # be merged together: + # + # class Article < ActiveRecord::Base + # default_scope where(:published => true) + # default_scope where(:rating => 'G') + # end + # + # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G' + # + # This is also the case with inheritance and module includes where the parent or module + # defines a <tt>default_scope</tt> and the child or including class defines a second one. + # # If you need to do more complex things with a default scope, you can alternatively # define it as a class method: # @@ -1214,36 +1232,8 @@ MSG # end # end def default_scope(scope = {}) - if default_scopes.length != 0 - ActiveSupport::Deprecation.warn <<-WARN -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 scope as a class method and use the standard techniques for sharing code (inheritance, mixins, etc.): - -class Post < ActiveRecord::Base - def self.default_scope - where(:published => true).where(:hidden => false) - end -end - WARN - end - scope = Proc.new if block_given? - self.default_scopes = default_scopes.dup << scope + self.default_scopes = default_scopes + [scope] end def build_default_scope #:nodoc: @@ -1484,7 +1474,20 @@ end # attributes but not yet saved (pass a hash with key names matching the associated table column names). # In both instances, valid attribute keys are determined by the column names of the associated table -- # hence you can't have attributes that aren't part of the table columns. - def initialize(attributes = nil) + # + # +initialize+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options + # in the +options+ parameter. + # + # ==== Examples + # # Instantiates a single new object + # User.new(:first_name => 'Jamie') + # + # # Instantiates a single new object using the :admin mass-assignment security scope + # User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin) + # + # # Instantiates a single new object bypassing mass-assignment security + # User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true) + def initialize(attributes = nil, options = {}) @attributes = attributes_from_column_definition @association_cache = {} @aggregation_cache = {} @@ -1500,7 +1503,8 @@ end set_serialized_attributes populate_with_current_scope_attributes - self.attributes = attributes unless attributes.nil? + + assign_attributes(attributes, options) if attributes result = yield self if block_given? run_callbacks :initialize diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index 1db397f584..093c30aa42 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -29,6 +29,14 @@ module ActiveRecord @query_cache_enabled = old end + def enable_query_cache! + @query_cache_enabled = true + end + + def disable_query_cache! + @query_cache_enabled = false + end + # Disable the query cache within the block. def uncached old, @query_cache_enabled = @query_cache_enabled, false diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index d3a054c29b..b6f838e49c 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -412,7 +412,7 @@ module ActiveRecord def tables(name = nil) tables = [] - execute("SHOW TABLES", name).each do |field| + execute("SHOW TABLES", 'SCHEMA').each do |field| tables << field.first end tables @@ -426,7 +426,7 @@ module ActiveRecord def indexes(table_name, name = nil) indexes = [] current_index = nil - result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name) + result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') result.each(:symbolize_keys => true, :as => :hash) do |row| if current_index != row[:Key_name] next if row[:Key_name] == PRIMARY # skip the primary key @@ -444,7 +444,7 @@ module ActiveRecord def columns(table_name, name = nil) sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}" columns = [] - result = execute(sql) + result = execute(sql, 'SCHEMA') result.each(:symbolize_keys => true, :as => :hash) { |field| columns << Mysql2Column.new(field[:Field], field[:Default], field[:Type], field[:Null] == "YES") } @@ -546,7 +546,7 @@ module ActiveRecord # Returns a table's primary key and belonging sequence. def pk_and_sequence_for(table) keys = [] - result = execute("describe #{quote_table_name(table)}") + result = execute("DESCRIBE #{quote_table_name(table)}", 'SCHEMA') result.each(:symbolize_keys => true, :as => :hash) do |row| keys << row[:Field] if row[:Key] == "PRI" end diff --git a/activerecord/lib/active_record/identity_map.rb b/activerecord/lib/active_record/identity_map.rb index 95a8e5cff7..4ad0b0d205 100644 --- a/activerecord/lib/active_record/identity_map.rb +++ b/activerecord/lib/active_record/identity_map.rb @@ -49,12 +49,15 @@ module ActiveRecord end def get(klass, primary_key) - obj = repository[klass.symbolized_base_class][primary_key] - if obj.is_a?(klass) - if ActiveRecord::Base.logger - ActiveRecord::Base.logger.debug "#{klass} with ID = #{primary_key} loaded from Identity Map" - end - obj + record = repository[klass.symbolized_base_class][primary_key] + + if record.is_a?(klass) + ActiveSupport::Notifications.instrument("identity.active_record", + :line => "From Identity Map (id: #{primary_key})", + :name => "#{klass} Loaded", + :connection_id => object_id) + + record else nil end diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb index d31e321440..3a015ee8c2 100644 --- a/activerecord/lib/active_record/log_subscriber.rb +++ b/activerecord/lib/active_record/log_subscriber.rb @@ -46,6 +46,15 @@ module ActiveRecord debug " #{name} #{sql}#{binds}" end + def identity(event) + return unless logger.debug? + + name = color(event.payload[:name], odd? ? CYAN : MAGENTA, true) + line = odd? ? color(event.payload[:line], nil, true) : event.payload[:line] + + debug " #{name} #{line}" + end + def odd? @odd_or_even = !@odd_or_even end diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb index d9f85a4e5e..929998eb85 100644 --- a/activerecord/lib/active_record/query_cache.rb +++ b/activerecord/lib/active_record/query_cache.rb @@ -27,10 +27,31 @@ module ActiveRecord @app = app end - def call(env) - ActiveRecord::Base.cache do - @app.call(env) + class BodyProxy # :nodoc: + def initialize(original_cache_value, target) + @original_cache_value = original_cache_value + @target = target + end + + def each(&block) + @target.each(&block) + end + + def close + @target.close if @target.respond_to?(:close) + ensure + unless @original_cache_value + ActiveRecord::Base.connection.disable_query_cache! + end end end + + def call(env) + old = ActiveRecord::Base.connection.query_cache_enabled + ActiveRecord::Base.connection.enable_query_cache! + + status, headers, body = @app.call(env) + [status, headers, BodyProxy.new(old, body)] + end end end diff --git a/activerecord/test/cases/identity_map_test.rb b/activerecord/test/cases/identity_map_test.rb index 2238529f0f..649715fbb5 100644 --- a/activerecord/test/cases/identity_map_test.rb +++ b/activerecord/test/cases/identity_map_test.rb @@ -1,4 +1,5 @@ require "cases/helper" + require 'models/developer' require 'models/project' require 'models/company' @@ -382,15 +383,6 @@ class IdentityMapTest < ActiveRecord::TestCase assert_not_nil post.title end - def test_log - log = StringIO.new - ActiveRecord::Base.logger = Logger.new(log) - ActiveRecord::Base.logger.level = Logger::DEBUG - Post.find 1 - Post.find 1 - assert_match(/Post with ID = 1 loaded from Identity Map/, log.string) - end - # Currently AR is not allowing changing primary key (see Persistence#update) # So we ignore it. If this changes, this test needs to be uncommented. # def test_updating_of_pkey diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index 5f55299065..c6c6079490 100644 --- a/activerecord/test/cases/log_subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -1,11 +1,14 @@ require "cases/helper" require "models/developer" +require "models/post" require "active_support/log_subscriber/test_helper" class LogSubscriberTest < ActiveRecord::TestCase include ActiveSupport::LogSubscriber::TestHelper include ActiveSupport::BufferedLogger::Severity + fixtures :posts + def setup @old_logger = ActiveRecord::Base.logger @using_identity_map = ActiveRecord::IdentityMap.enabled? @@ -91,4 +94,13 @@ class LogSubscriberTest < ActiveRecord::TestCase def test_initializes_runtime Thread.new { assert_equal 0, ActiveRecord::LogSubscriber.runtime }.join end + + def test_log + ActiveRecord::IdentityMap.use do + Post.find 1 + Post.find 1 + end + wait + assert_match(/From Identity Map/, @logger.logged(:debug).last) + end end diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb index 2c051bff84..fbbae99e8b 100644 --- a/activerecord/test/cases/mass_assignment_security_test.rb +++ b/activerecord/test/cases/mass_assignment_security_test.rb @@ -5,7 +5,63 @@ require 'models/keyboard' require 'models/task' require 'models/person' + +module MassAssignmentTestHelpers + def setup + # another AR test modifies the columns which causes issues with create calls + TightPerson.reset_column_information + LoosePerson.reset_column_information + end + + def attributes_hash + { + :id => 5, + :first_name => 'Josh', + :gender => 'm', + :comments => 'rides a sweet bike' + } + end + + def assert_default_attributes(person, create = false) + unless create + assert_nil person.id + else + assert !!person.id + end + assert_equal 'Josh', person.first_name + assert_equal 'm', person.gender + assert_nil person.comments + end + + def assert_admin_attributes(person, create = false) + unless create + assert_nil person.id + else + assert !!person.id + end + assert_equal 'Josh', person.first_name + assert_equal 'm', person.gender + assert_equal 'rides a sweet bike', person.comments + end + + def assert_all_attributes(person) + assert_equal 5, person.id + assert_equal 'Josh', person.first_name + assert_equal 'm', person.gender + assert_equal 'rides a sweet bike', person.comments + end +end + +module MassAssignmentRelationTestHelpers + def setup + super + @person = LoosePerson.create(attributes_hash) + end +end + + class MassAssignmentSecurityTest < ActiveRecord::TestCase + include MassAssignmentTestHelpers def test_customized_primary_key_remains_protected subscriber = Subscriber.new(:nick => 'webster123', :name => 'nice try') @@ -35,60 +91,114 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase p = LoosePerson.new p.assign_attributes(attributes_hash) - assert_equal nil, p.id - assert_equal 'Josh', p.first_name - assert_equal 'm', p.gender - assert_equal nil, p.comments + assert_default_attributes(p) end def test_assign_attributes_skips_mass_assignment_security_protection_when_without_protection_is_used p = LoosePerson.new p.assign_attributes(attributes_hash, :without_protection => true) - assert_equal 5, p.id - assert_equal 'Josh', p.first_name - assert_equal 'm', p.gender - assert_equal 'rides a sweet bike', p.comments + assert_all_attributes(p) end def test_assign_attributes_with_default_scope_and_attr_protected_attributes p = LoosePerson.new p.assign_attributes(attributes_hash, :as => :default) - assert_equal nil, p.id - assert_equal 'Josh', p.first_name - assert_equal 'm', p.gender - assert_equal nil, p.comments + assert_default_attributes(p) end def test_assign_attributes_with_admin_scope_and_attr_protected_attributes p = LoosePerson.new p.assign_attributes(attributes_hash, :as => :admin) - assert_equal nil, p.id - assert_equal 'Josh', p.first_name - assert_equal 'm', p.gender - assert_equal 'rides a sweet bike', p.comments + assert_admin_attributes(p) end def test_assign_attributes_with_default_scope_and_attr_accessible_attributes p = TightPerson.new p.assign_attributes(attributes_hash, :as => :default) - assert_equal nil, p.id - assert_equal 'Josh', p.first_name - assert_equal 'm', p.gender - assert_equal nil, p.comments + assert_default_attributes(p) end def test_assign_attributes_with_admin_scope_and_attr_accessible_attributes p = TightPerson.new p.assign_attributes(attributes_hash, :as => :admin) - assert_equal nil, p.id - assert_equal 'Josh', p.first_name - assert_equal 'm', p.gender - assert_equal 'rides a sweet bike', p.comments + assert_admin_attributes(p) + end + + def test_new_with_attr_accessible_attributes + p = TightPerson.new(attributes_hash) + + assert_default_attributes(p) + end + + def test_new_with_attr_protected_attributes + p = LoosePerson.new(attributes_hash) + + assert_default_attributes(p) + end + + def test_create_with_attr_accessible_attributes + p = TightPerson.create(attributes_hash) + + assert_default_attributes(p, true) + end + + def test_create_with_attr_protected_attributes + p = LoosePerson.create(attributes_hash) + + assert_default_attributes(p, true) + end + + def test_new_with_admin_scope_with_attr_accessible_attributes + p = TightPerson.new(attributes_hash, :as => :admin) + + assert_admin_attributes(p) + end + + def test_new_with_admin_scope_with_attr_protected_attributes + p = LoosePerson.new(attributes_hash, :as => :admin) + + assert_admin_attributes(p) + end + + def test_create_with_admin_scope_with_attr_accessible_attributes + p = TightPerson.create(attributes_hash, :as => :admin) + + assert_admin_attributes(p, true) + end + + def test_create_with_admin_scope_with_attr_protected_attributes + p = LoosePerson.create(attributes_hash, :as => :admin) + + assert_admin_attributes(p, true) + end + + def test_new_with_without_protection_with_attr_accessible_attributes + p = TightPerson.new(attributes_hash, :without_protection => true) + + assert_all_attributes(p) + end + + def test_new_with_without_protection_with_attr_protected_attributes + p = LoosePerson.new(attributes_hash, :without_protection => true) + + assert_all_attributes(p) + end + + def test_create_with_without_protection_with_attr_accessible_attributes + p = TightPerson.create(attributes_hash, :without_protection => true) + + assert_all_attributes(p) + end + + def test_create_with_without_protection_with_attr_protected_attributes + p = LoosePerson.create(attributes_hash, :without_protection => true) + + assert_all_attributes(p) end def test_protection_against_class_attribute_writers @@ -101,14 +211,268 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase end end - private +end - def attributes_hash - { - :id => 5, - :first_name => 'Josh', - :gender => 'm', - :comments => 'rides a sweet bike' - } + +class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase + include MassAssignmentTestHelpers + include MassAssignmentRelationTestHelpers + + # build + + def test_has_one_build_with_attr_protected_attributes + best_friend = @person.build_best_friend(attributes_hash) + assert_default_attributes(best_friend) + end + + def test_has_one_build_with_attr_accessible_attributes + best_friend = @person.build_best_friend(attributes_hash) + assert_default_attributes(best_friend) + end + + def test_has_one_build_with_admin_scope_with_attr_protected_attributes + best_friend = @person.build_best_friend(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend) + end + + def test_has_one_build_with_admin_scope_with_attr_accessible_attributes + best_friend = @person.build_best_friend(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend) + end + + def test_has_one_build_without_protection + best_friend = @person.build_best_friend(attributes_hash, :without_protection => true) + assert_all_attributes(best_friend) + end + + # create + + def test_has_one_create_with_attr_protected_attributes + best_friend = @person.create_best_friend(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_attr_accessible_attributes + best_friend = @person.create_best_friend(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_admin_scope_with_attr_protected_attributes + best_friend = @person.create_best_friend(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) + end + + def test_has_one_create_with_admin_scope_with_attr_accessible_attributes + best_friend = @person.create_best_friend(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) end -end
\ No newline at end of file + + def test_has_one_create_without_protection + best_friend = @person.create_best_friend(attributes_hash, :without_protection => true) + assert_all_attributes(best_friend) + end + + # create! + + def test_has_one_create_with_bang_with_attr_protected_attributes + best_friend = @person.create_best_friend!(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_with_attr_accessible_attributes + best_friend = @person.create_best_friend!(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes + best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes + best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_without_protection + best_friend = @person.create_best_friend!(attributes_hash, :without_protection => true) + assert_all_attributes(best_friend) + end + +end + + +class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase + include MassAssignmentTestHelpers + include MassAssignmentRelationTestHelpers + + # build + + def test_has_one_build_with_attr_protected_attributes + best_friend = @person.build_best_friend_of(attributes_hash) + assert_default_attributes(best_friend) + end + + def test_has_one_build_with_attr_accessible_attributes + best_friend = @person.build_best_friend_of(attributes_hash) + assert_default_attributes(best_friend) + end + + def test_has_one_build_with_admin_scope_with_attr_protected_attributes + best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend) + end + + def test_has_one_build_with_admin_scope_with_attr_accessible_attributes + best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend) + end + + def test_has_one_build_without_protection + best_friend = @person.build_best_friend_of(attributes_hash, :without_protection => true) + assert_all_attributes(best_friend) + end + + # create + + def test_has_one_create_with_attr_protected_attributes + best_friend = @person.create_best_friend_of(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_attr_accessible_attributes + best_friend = @person.create_best_friend_of(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_admin_scope_with_attr_protected_attributes + best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) + end + + def test_has_one_create_with_admin_scope_with_attr_accessible_attributes + best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) + end + + def test_has_one_create_without_protection + best_friend = @person.create_best_friend_of(attributes_hash, :without_protection => true) + assert_all_attributes(best_friend) + end + + # create! + + def test_has_one_create_with_bang_with_attr_protected_attributes + best_friend = @person.create_best_friend!(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_with_attr_accessible_attributes + best_friend = @person.create_best_friend!(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes + best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes + best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_without_protection + best_friend = @person.create_best_friend!(attributes_hash, :without_protection => true) + assert_all_attributes(best_friend) + end + +end + + +class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase + include MassAssignmentTestHelpers + include MassAssignmentRelationTestHelpers + + # build + + def test_has_one_build_with_attr_protected_attributes + best_friend = @person.best_friends.build(attributes_hash) + assert_default_attributes(best_friend) + end + + def test_has_one_build_with_attr_accessible_attributes + best_friend = @person.best_friends.build(attributes_hash) + assert_default_attributes(best_friend) + end + + def test_has_one_build_with_admin_scope_with_attr_protected_attributes + best_friend = @person.best_friends.build(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend) + end + + def test_has_one_build_with_admin_scope_with_attr_accessible_attributes + best_friend = @person.best_friends.build(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend) + end + + def test_has_one_build_without_protection + best_friend = @person.best_friends.build(attributes_hash, :without_protection => true) + assert_all_attributes(best_friend) + end + + # create + + def test_has_one_create_with_attr_protected_attributes + best_friend = @person.best_friends.create(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_attr_accessible_attributes + best_friend = @person.best_friends.create(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_admin_scope_with_attr_protected_attributes + best_friend = @person.best_friends.create(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) + end + + def test_has_one_create_with_admin_scope_with_attr_accessible_attributes + best_friend = @person.best_friends.create(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) + end + + def test_has_one_create_without_protection + best_friend = @person.best_friends.create(attributes_hash, :without_protection => true) + assert_all_attributes(best_friend) + end + + # create! + + def test_has_one_create_with_bang_with_attr_protected_attributes + best_friend = @person.best_friends.create!(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_with_attr_accessible_attributes + best_friend = @person.best_friends.create!(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes + best_friend = @person.best_friends.create!(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes + best_friend = @person.best_friends.create!(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_without_protection + best_friend = @person.best_friends.create!(attributes_hash, :without_protection => true) + assert_all_attributes(best_friend) + end + +end diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index 287f7e255b..b2e40c6b22 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -10,6 +10,59 @@ class QueryCacheTest < ActiveRecord::TestCase def setup Task.connection.clear_query_cache + ActiveRecord::Base.connection.disable_query_cache! + end + + def test_middleware_delegates + called = false + mw = ActiveRecord::QueryCache.new lambda { |env| + called = true + } + mw.call({}) + assert called, 'middleware should delegate' + end + + def test_middleware_caches + mw = ActiveRecord::QueryCache.new lambda { |env| + Task.find 1 + Task.find 1 + assert_equal 1, ActiveRecord::Base.connection.query_cache.length + } + mw.call({}) + end + + def test_cache_enabled_during_call + assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off' + + mw = ActiveRecord::QueryCache.new lambda { |env| + assert ActiveRecord::Base.connection.query_cache_enabled, 'cache on' + } + mw.call({}) + end + + def test_cache_on_during_body_write + streaming = Class.new do + def each + yield ActiveRecord::Base.connection.query_cache_enabled + end + end + + mw = ActiveRecord::QueryCache.new lambda { |env| + [200, {}, streaming.new] + } + body = mw.call({}).last + body.each { |x| assert x, 'cache should be on' } + body.close + assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled' + end + + def test_cache_off_after_close + mw = ActiveRecord::QueryCache.new lambda { |env| } + body = mw.call({}).last + + assert ActiveRecord::Base.connection.query_cache_enabled, 'cache enabled' + body.close + assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled' end def test_find_queries diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index 2ed676fe69..864b3d4846 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -355,6 +355,12 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal 50000, wheres[:salary] end + def test_default_scope_with_module_includes + wheres = ModuleIncludedPoorDeveloperCalledJamis.scoped.where_values_hash + assert_equal "Jamis", wheres[:name] + assert_equal 50000, wheres[:salary] + end + def test_default_scope_with_multiple_calls wheres = MultiplePoorDeveloperCalledJamis.scoped.where_values_hash assert_equal "Jamis", wheres[:name] @@ -456,18 +462,4 @@ class DefaultScopingTest < ActiveRecord::TestCase assert DeveloperCalledJamis.unscoped.poor.include?(developers(:david).becomes(DeveloperCalledJamis)) assert_equal 10, DeveloperCalledJamis.unscoped.poor.length end - - def test_multiple_default_scope_calls_are_deprecated - klass = Class.new(ActiveRecord::Base) - - assert_not_deprecated do - klass.send(:default_scope, :foo => :bar) - end - - assert_deprecated do - klass.send(:default_scope, :foo => :bar) - end - - assert_equal 2, klass.default_scopes.length - end end diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 10701dd6fd..152f804e16 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -129,28 +129,40 @@ end class DeveloperCalledJamis < ActiveRecord::Base self.table_name = 'developers' + default_scope where(:name => 'Jamis') scope :poor, where('salary < 150000') end class PoorDeveloperCalledJamis < ActiveRecord::Base self.table_name = 'developers' + default_scope where(:name => 'Jamis', :salary => 50000) end class InheritedPoorDeveloperCalledJamis < DeveloperCalledJamis self.table_name = 'developers' - ActiveSupport::Deprecation.silence do - default_scope where(:salary => 50000) - end + default_scope where(:salary => 50000) end class MultiplePoorDeveloperCalledJamis < ActiveRecord::Base self.table_name = 'developers' + default_scope where(:name => 'Jamis') + default_scope where(:salary => 50000) +end - ActiveSupport::Deprecation.silence do - default_scope where(:salary => 50000) - end +module SalaryDefaultScope + extend ActiveSupport::Concern + + included { default_scope where(:salary => 50000) } end + +class ModuleIncludedPoorDeveloperCalledJamis < DeveloperCalledJamis + self.table_name = 'developers' + + include SalaryDefaultScope +end + + diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index 9c4794902d..a58c9bf572 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -1,6 +1,6 @@ class Person < ActiveRecord::Base has_many :readers - has_one :reader + has_one :reader has_many :posts, :through => :readers has_many :posts_with_no_comments, :through => :readers, :source => :post, :include => :comments, :conditions => 'comments.id is null' @@ -8,23 +8,23 @@ class Person < ActiveRecord::Base has_many :references has_many :bad_references has_many :fixed_bad_references, :conditions => { :favourite => true }, :class_name => 'BadReference' - has_one :favourite_reference, :class_name => 'Reference', :conditions => ['favourite=?', true] + has_one :favourite_reference, :class_name => 'Reference', :conditions => ['favourite=?', true] has_many :posts_with_comments_sorted_by_comment_id, :through => :readers, :source => :post, :include => :comments, :order => 'comments.id' has_many :jobs, :through => :references - has_many :jobs_with_dependent_destroy, :source => :job, :through => :references, :dependent => :destroy + has_many :jobs_with_dependent_destroy, :source => :job, :through => :references, :dependent => :destroy has_many :jobs_with_dependent_delete_all, :source => :job, :through => :references, :dependent => :delete_all - has_many :jobs_with_dependent_nullify, :source => :job, :through => :references, :dependent => :nullify + has_many :jobs_with_dependent_nullify, :source => :job, :through => :references, :dependent => :nullify belongs_to :primary_contact, :class_name => 'Person' has_many :agents, :class_name => 'Person', :foreign_key => 'primary_contact_id' has_many :agents_of_agents, :through => :agents, :source => :agents belongs_to :number1_fan, :class_name => 'Person' - has_many :agents_posts, :through => :agents, :source => :posts + has_many :agents_posts, :through => :agents, :source => :posts has_many :agents_posts_authors, :through => :agents_posts, :source => :author - scope :males, :conditions => { :gender => 'M' } + scope :males, :conditions => { :gender => 'M' } scope :females, :conditions => { :gender => 'F' } end @@ -56,14 +56,25 @@ class LoosePerson < ActiveRecord::Base attr_protected :comments attr_protected :as => :admin + + has_one :best_friend, :class_name => 'LoosePerson', :foreign_key => :best_friend_id + belongs_to :best_friend_of, :class_name => 'LoosePerson', :foreign_key => :best_friend_of_id + + has_many :best_friends, :class_name => 'LoosePerson', :foreign_key => :best_friend_id end class LooseDescendant < LoosePerson; end class TightPerson < ActiveRecord::Base self.table_name = 'people' + attr_accessible :first_name, :gender attr_accessible :first_name, :gender, :comments, :as => :admin + + has_one :best_friend, :class_name => 'TightPerson', :foreign_key => :best_friend_id + belongs_to :best_friend_of, :class_name => 'TightPerson', :foreign_key => :best_friend_of_id + + has_many :best_friends, :class_name => 'TightPerson', :foreign_key => :best_friend_id end class TightDescendant < TightPerson; end
\ No newline at end of file diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index ceadb05644..9479242e4f 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -438,6 +438,8 @@ ActiveRecord::Schema.define do t.references :number1_fan t.integer :lock_version, :null => false, :default => 0 t.string :comments + t.references :best_friend + t.references :best_friend_of t.timestamps end diff --git a/activeresource/examples/performance.rb b/activeresource/examples/performance.rb new file mode 100644 index 0000000000..e4df7a38a4 --- /dev/null +++ b/activeresource/examples/performance.rb @@ -0,0 +1,70 @@ +require 'rubygems' +require 'active_resource' +require 'benchmark' + +TIMES = (ENV['N'] || 10_000).to_i + +# deep nested resource +attrs = { + :id => 1, + :name => 'Luis', + :age => 21, + :friends => [ + { + :name => 'JK', + :age => 24, + :colors => ['red', 'green', 'blue'], + :brothers => [ + { + :name => 'Mateo', + :age => 35, + :children => [{ :name => 'Edith', :age => 5 }, { :name => 'Martha', :age => 4 }] + }, + { + :name => 'Felipe', + :age => 33, + :children => [{ :name => 'Bryan', :age => 1 }, { :name => 'Luke', :age => 0 }] + } + ] + }, + { + :name => 'Eduardo', + :age => 20, + :colors => [], + :brothers => [ + { + :name => 'Sebas', + :age => 23, + :children => [{ :name => 'Andres', :age => 0 }, { :name => 'Jorge', :age => 2 }] + }, + { + :name => 'Elsa', + :age => 19, + :children => [{ :name => 'Natacha', :age => 1 }] + }, + { + :name => 'Milena', + :age => 16, + :children => [] + } + ] + } + ] +} + +class Customer < ActiveResource::Base + self.site = "http://37s.sunrise.i:3000" +end + +module Nested + class Customer < ActiveResource::Base + self.site = "http://37s.sunrise.i:3000" + end +end + +Benchmark.bm(40) do |x| + x.report('Model.new (instantiation)') { TIMES.times { Customer.new } } + x.report('Nested::Model.new (instantiation)') { TIMES.times { Nested::Customer.new } } + x.report('Model.new (setting attributes)') { TIMES.times { Customer.new attrs } } + x.report('Nested::Model.new (setting attributes)') { TIMES.times { Nested::Customer.new attrs } } +end diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index 160763779e..7f2a844723 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -1239,9 +1239,10 @@ module ActiveResource @attributes[key.to_s] = case value when Array - resource = find_or_create_resource_for_collection(key) + resource = nil value.map do |attrs| if attrs.is_a?(Hash) + resource ||= find_or_create_resource_for_collection(key) resource.new(attrs) else attrs.duplicable? ? attrs.dup : attrs @@ -1251,7 +1252,7 @@ module ActiveResource resource = find_or_create_resource_for(key) resource.new(value) else - value.dup rescue value + value.duplicable? ? value.dup : value end end self @@ -1367,36 +1368,44 @@ module ActiveResource end # Tries to find a resource in a non empty list of nested modules - # Raises a NameError if it was not found in any of the given nested modules - def find_resource_in_modules(resource_name, module_names) + # if it fails, then the resource is created + def find_or_create_resource_in_modules(resource_name, module_names) receiver = Object namespaces = module_names[0, module_names.size-1].map do |module_name| receiver = receiver.const_get(module_name) end const_args = RUBY_VERSION < "1.9" ? [resource_name] : [resource_name, false] if namespace = namespaces.reverse.detect { |ns| ns.const_defined?(*const_args) } - return namespace.const_get(*const_args) + namespace.const_get(*const_args) else - raise NameError + create_resource_for(resource_name) end end # Tries to find a resource for a given name; if it fails, then the resource is created def find_or_create_resource_for(name) resource_name = name.to_s.camelize - ancestors = self.class.name.split("::") - if ancestors.size > 1 - find_resource_in_modules(resource_name, ancestors) - else - self.class.const_get(resource_name) - end - rescue NameError + const_args = RUBY_VERSION < "1.9" ? [resource_name] : [resource_name, false] if self.class.const_defined?(*const_args) - resource = self.class.const_get(*const_args) + self.class.const_get(*const_args) else - resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base)) + ancestors = self.class.name.split("::") + if ancestors.size > 1 + find_or_create_resource_in_modules(resource_name, ancestors) + else + if Object.const_defined?(*const_args) + Object.const_get(*const_args) + else + create_resource_for(resource_name) + end + end end + end + + # Create and return a class definition for a resource inside the current resource + def create_resource_for(resource_name) + resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base)) resource.prefix = self.class.prefix resource.site = self.class.site resource diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb index bfc2024529..05da50e150 100644 --- a/activesupport/lib/active_support/testing/assertions.rb +++ b/activesupport/lib/active_support/testing/assertions.rb @@ -45,17 +45,17 @@ module ActiveSupport # post :delete, :id => ... # end def assert_difference(expression, difference = 1, message = nil, &block) - b = block.send(:binding) - exps = Array.wrap(expression) - before = exps.map { |e| e.respond_to?(:call) ? e.call : eval(e, b) } + exps = Array.wrap(expression).map { |e| + e.respond_to?(:call) ? e : lambda { eval(e, block.binding) } + } + before = exps.map { |e| e.call } yield exps.each_with_index do |e, i| error = "#{e.inspect} didn't change by #{difference}" error = "#{message}.\n#{error}" if message - actual = e.respond_to?(:call) ? e.call : eval(e, b) - assert_equal(before[i] + difference, actual, error) + assert_equal(before[i] + difference, e.call, error) end end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 324199e71c..689ef921e1 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -132,14 +132,12 @@ module Rails gem 'rails', :path => '#{Rails::Generators::RAILS_DEV_PATH}' gem 'arel', :git => 'git://github.com/rails/arel.git' gem 'rack', :git => 'git://github.com/rack/rack.git' - gem 'sprockets', :git => 'git://github.com/sstephenson/sprockets.git' GEMFILE elsif options.edge? <<-GEMFILE.strip_heredoc gem 'rails', :git => 'git://github.com/rails/rails.git' gem 'arel', :git => 'git://github.com/rails/arel.git' gem 'rack', :git => 'git://github.com/rack/rack.git' - gem 'sprockets', :git => 'git://github.com/sstephenson/sprockets.git' GEMFILE else <<-GEMFILE.strip_heredoc @@ -149,7 +147,6 @@ module Rails # gem 'rails', :git => 'git://github.com/rails/rails.git' # gem 'arel', :git => 'git://github.com/rails/arel.git' # gem 'rack', :git => 'git://github.com/rack/rack.git' - # gem 'sprockets', :git => 'git://github.com/sstephenson/sprockets.git' GEMFILE end end @@ -167,7 +164,7 @@ module Rails else options[:database] end end - + def gem_for_ruby_debugger if RUBY_VERSION < "1.9.2" "gem 'ruby-debug'" @@ -175,7 +172,7 @@ module Rails "gem 'ruby-debug19', :require => 'ruby-debug'" end end - + def gem_for_turn unless RUBY_VERSION < "1.9.2" <<-GEMFILE.strip_heredoc @@ -200,7 +197,7 @@ module Rails empty_directory(destination, config) git_keep(destination) end - + def git_keep(destination) create_file("#{destination}/.gitkeep") unless options[:skip_git] end @@ -216,4 +213,4 @@ module Rails end end end -end
\ No newline at end of file +end |