diff options
-rw-r--r-- | activerecord/CHANGELOG.md | 33 | ||||
-rw-r--r-- | activerecord/lib/active_record/attribute_methods/dirty.rb | 20 | ||||
-rw-r--r-- | activerecord/lib/active_record/relation.rb | 2 | ||||
-rw-r--r-- | activerecord/lib/active_record/store.rb | 16 | ||||
-rw-r--r-- | activerecord/lib/active_record/tasks/database_tasks.rb | 27 | ||||
-rw-r--r-- | activerecord/test/cases/nested_attributes_test.rb | 10 | ||||
-rw-r--r-- | activerecord/test/cases/store_test.rb | 6 | ||||
-rw-r--r-- | activesupport/lib/active_support/hash_with_indifferent_access.rb | 3 | ||||
-rw-r--r-- | activesupport/lib/active_support/log_subscriber.rb | 33 | ||||
-rw-r--r-- | activesupport/lib/active_support/notifications/instrumenter.rb | 16 | ||||
-rw-r--r-- | activesupport/test/core_ext/hash_ext_test.rb | 7 | ||||
-rw-r--r-- | activesupport/test/notifications_test.rb | 4 |
12 files changed, 142 insertions, 35 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index a617cef6a1..10bfe1945a 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,8 +1,37 @@ ## Rails 4.0.0 (unreleased) ## -* `composed_of` has removed. You'll have to write your own accessor +* Added `stored_attributes` hash which contains the attributes stored using + ActiveRecord::Store. This allows you to retrieve the list of attributes + you've defined. + + class User < ActiveRecord::Base + store :settings, accessors: [:color, :homepage] + end + + User.stored_attributes[:settings] # [:color, :homepage] + + *Joost Baaij & Carlos Antonio da Silva* + +* `composed_of` was removed. You'll have to write your own accessor and mutator methods if you'd like to use value objects to represent some - portion of your models. + portion of your models. So, instead of: + + class Person < ActiveRecord::Base + composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ] + end + + you could write something like this: + + def address + @address ||= Address.new(address_street, address_city) + end + + def address=(address) + self[:address_street] = @address.street + self[:address_city] = @address.city + + @address = address + end *Steve Klabnik* diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index f8a40ad520..a85b6b3b82 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -77,11 +77,8 @@ module ActiveRecord def _field_changed?(attr, old, value) if column = column_for_attribute(attr) - if column.number? && column.null && (old.nil? || old == 0) && value.blank? - # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values. - # Hence we don't record it as a change if the value changes from nil to ''. - # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll - # be typecast back to 0 (''.to_i => 0) + if column.number? && (changes_from_nil_to_empty_string?(column, old, value) || + changes_from_zero_to_string?(column, old, value)) value = nil else value = column.type_cast(value) @@ -90,6 +87,19 @@ module ActiveRecord old != value end + + def changes_from_nil_to_empty_string?(column, old, value) + # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values. + # Hence we don't record it as a change if the value changes from nil to ''. + # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll + # be typecast back to 0 (''.to_i => 0) + column.null && (old.nil? || old == 0) && value.blank? + end + + def changes_from_zero_to_string?(column, old, value) + # For columns with old 0 and value non-empty string + old == 0 && value.present? && value != '0' + end end end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 05ced3299b..fe3aa00a74 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -300,7 +300,7 @@ module ActiveRecord # Person.update(people.keys, people.values) def update(id, attributes) if id.is_a?(Array) - id.each.with_index.map {|one_id, idx| update(one_id, attributes[idx])} + id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) } else object = find(id) object.update_attributes(attributes) diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb index 542cb3187a..d13491502e 100644 --- a/activerecord/lib/active_record/store.rb +++ b/activerecord/lib/active_record/store.rb @@ -1,4 +1,6 @@ +require 'active_support/concern' require 'active_support/core_ext/hash/indifferent_access' +require 'active_support/core_ext/class/attribute' module ActiveRecord # Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column. @@ -33,9 +35,18 @@ module ActiveRecord # class SuperUser < User # store_accessor :settings, :privileges, :servants # end + # + # The stored attribute names can be retrieved using +stored_attributes+. + # + # User.stored_attributes[:settings] # [:color, :homepage] module Store extend ActiveSupport::Concern + included do + class_attribute :stored_attributes + self.stored_attributes = {} + end + module ClassMethods def store(store_attribute, options = {}) serialize store_attribute, IndifferentCoder.new(options[:coder]) @@ -43,7 +54,8 @@ module ActiveRecord end def store_accessor(store_attribute, *keys) - keys.flatten.each do |key| + keys = keys.flatten + keys.each do |key| define_method("#{key}=") do |value| initialize_store_attribute(store_attribute) send(store_attribute)[key] = value @@ -55,6 +67,8 @@ module ActiveRecord send(store_attribute)[key] end end + + self.stored_attributes[store_attribute] = keys end end diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 8e05c1a439..24fe4134e0 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -1,6 +1,7 @@ module ActiveRecord module Tasks # :nodoc: - class DatabaseTasks # :nodoc: + module DatabaseTasks # :nodoc: + extend self TASKS_PATTERNS = { /mysql/ => ActiveRecord::Tasks::MySQLDatabaseTasks, @@ -9,7 +10,7 @@ module ActiveRecord } LOCAL_HOSTS = ['127.0.0.1', 'localhost'] - def self.create(*arguments) + def create(*arguments) configuration = arguments.first class_for_adapter(configuration['adapter']).new(*arguments).create rescue Exception => error @@ -17,18 +18,18 @@ module ActiveRecord $stderr.puts "Couldn't create database for #{configuration.inspect}" end - def self.create_all + def create_all each_local_configuration { |configuration| create configuration } end - def self.create_current(environment = Rails.env) + def create_current(environment = Rails.env) each_current_configuration(environment) { |configuration| create configuration } ActiveRecord::Base.establish_connection environment end - def self.drop(*arguments) + def drop(*arguments) configuration = arguments.first class_for_adapter(configuration['adapter']).new(*arguments).drop rescue Exception => error @@ -36,28 +37,28 @@ module ActiveRecord $stderr.puts "Couldn't drop #{configuration['database']}" end - def self.drop_all + def drop_all each_local_configuration { |configuration| drop configuration } end - def self.drop_current(environment = Rails.env) + def drop_current(environment = Rails.env) each_current_configuration(environment) { |configuration| drop configuration } end - def self.purge(configuration) + def purge(configuration) class_for_adapter(configuration['adapter']).new(configuration).purge end private - def self.class_for_adapter(adapter) + def class_for_adapter(adapter) key = TASKS_PATTERNS.keys.detect { |pattern| adapter[pattern] } TASKS_PATTERNS[key] end - def self.each_current_configuration(environment) + def each_current_configuration(environment) environments = [environment] environments << 'test' if environment.development? @@ -67,7 +68,7 @@ module ActiveRecord end end - def self.each_local_configuration + def each_local_configuration ActiveRecord::Base.configurations.each_value do |configuration| next unless configuration['database'] @@ -79,9 +80,9 @@ module ActiveRecord end end - def self.local_database?(configuration) + def local_database?(configuration) configuration['host'].in?(LOCAL_HOSTS) || configuration['host'].blank? end end end -end
\ No newline at end of file +end diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 0559bbbe9a..3daa033ed0 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -781,6 +781,16 @@ module NestedAttributesOnACollectionAssociationTests assert_nothing_raised(NoMethodError) { @pirate.save! } end + def test_numeric_colum_changes_from_zero_to_no_empty_string + Man.accepts_nested_attributes_for(:interests) + Interest.validates_numericality_of(:zine_id) + man = Man.create(:name => 'John') + interest = man.interests.create(:topic=>'bar',:zine_id => 0) + assert interest.save + + assert !man.update_attributes({:interests_attributes => { :id => interest.id, :zine_id => 'foo' }}) + end + private def association_setter diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb index 79476ed2a4..e401dd4b12 100644 --- a/activerecord/test/cases/store_test.rb +++ b/activerecord/test/cases/store_test.rb @@ -13,7 +13,7 @@ class StoreTest < ActiveRecord::TestCase assert_equal 'black', @john.color assert_nil @john.homepage end - + test "writing store attributes through accessors" do @john.color = 'red' @john.homepage = '37signals.com' @@ -111,4 +111,8 @@ class StoreTest < ActiveRecord::TestCase @john.is_a_good_guy = false assert_equal false, @john.is_a_good_guy end + + test "stored attributes are returned" do + assert_equal [:color, :homepage], Admin::User.stored_attributes[:settings] + end end diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 6e1c0da991..3e6c8893e9 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -164,7 +164,8 @@ module ActiveSupport if value.is_a? Hash value.nested_under_indifferent_access elsif value.is_a?(Array) - value.dup.replace(value.map { |e| convert_value(e) }) + value = value.dup if value.frozen? + value.map! { |e| convert_value(e) } else value end diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb index d5f0e3fa6c..2e423b0364 100644 --- a/activesupport/lib/active_support/log_subscriber.rb +++ b/activesupport/lib/active_support/log_subscriber.rb @@ -61,7 +61,7 @@ module ActiveSupport @@flushable_loggers = nil log_subscriber.public_methods(false).each do |event| - next if :call == event + next if %w{ start finish }.include?(event.to_s) notifier.subscribe("#{event}.#{namespace}", log_subscriber) end @@ -86,14 +86,35 @@ module ActiveSupport end end - def call(message, *args) + def initialize + @event_stack = Hash.new { |h,id| + h[id] = Hash.new { |ids,name| ids[name] = [] } + } + super + end + + def start(name, id, payload) + return unless logger + + e = ActiveSupport::Notifications::Event.new(name, Time.now, nil, id, payload) + parent = @event_stack[id][name].last + parent << e if parent + + @event_stack[id][name].push e + end + + def finish(name, id, payload) return unless logger - method = message.split('.').first + finished = Time.now + event = @event_stack[id][name].pop + event.end = finished + + method = name.split('.').first begin - send(method, ActiveSupport::Notifications::Event.new(message, *args)) - rescue => e - logger.error "Could not log #{message.inspect} event. #{e.class}: #{e.message} #{e.backtrace}" + send(method, event) + rescue Exception => e + logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}" end end diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb index 58e292c658..7dfea4bb4b 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -31,7 +31,8 @@ module ActiveSupport end class Event - attr_reader :name, :time, :end, :transaction_id, :payload, :duration + attr_reader :name, :time, :transaction_id, :payload, :children + attr_accessor :end def initialize(name, start, ending, transaction_id, payload) @name = name @@ -39,12 +40,19 @@ module ActiveSupport @time = start @transaction_id = transaction_id @end = ending - @duration = 1000.0 * (@end - @time) + @children = [] + end + + def duration + 1000.0 * (self.end - time) + end + + def <<(event) + @children << event end def parent_of?(event) - start = (time - event.time) * 1000 - start <= 0 && (start + duration >= event.duration) + @children.include? event end end end diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 5d422ce5ad..4dc9f57038 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -457,6 +457,13 @@ class HashExtTest < ActiveSupport::TestCase assert_equal '1234', roundtrip.default end + def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_access + hash = HashWithIndifferentAccess.new {|h, k| h[k] = []} + hash[:a] << 1 + + assert_equal [1], hash[:a] + end + def test_indifferent_hash_with_array_of_hashes hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] }}.with_indifferent_access assert_equal "1", hash[:urls][:url].first[:address] diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb index fc9fa90d07..bcb393c7bc 100644 --- a/activesupport/test/notifications_test.rb +++ b/activesupport/test/notifications_test.rb @@ -221,13 +221,15 @@ module Notifications assert_equal Hash[:payload => :bar], event.payload end - def test_event_is_parent_based_on_time_frame + def test_event_is_parent_based_on_children time = Time.utc(2009, 01, 01, 0, 0, 1) parent = event(:foo, Time.utc(2009), Time.utc(2009) + 100, random_id, {}) child = event(:foo, time, time + 10, random_id, {}) not_child = event(:foo, time, time + 100, random_id, {}) + parent.children << child + assert parent.parent_of?(child) assert !child.parent_of?(parent) assert !parent.parent_of?(not_child) |