aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activerecord/CHANGELOG.md33
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb20
-rw-r--r--activerecord/lib/active_record/relation.rb2
-rw-r--r--activerecord/lib/active_record/store.rb16
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb27
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb10
-rw-r--r--activerecord/test/cases/store_test.rb6
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb3
-rw-r--r--activesupport/lib/active_support/log_subscriber.rb33
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb16
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb7
-rw-r--r--activesupport/test/notifications_test.rb4
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)