diff options
Diffstat (limited to 'activerecord/lib/active_record')
9 files changed, 119 insertions, 74 deletions
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 |