aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport')
-rw-r--r--activesupport/CHANGELOG10
-rw-r--r--activesupport/lib/active_support.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/array/grouping.rb47
-rw-r--r--activesupport/lib/active_support/core_ext/module/introspection.rb25
-rw-r--r--activesupport/lib/active_support/core_ext/object/instance_variables.rb2
-rw-r--r--activesupport/lib/active_support/dependencies.rb4
-rw-r--r--activesupport/lib/active_support/json/encoders/date_time.rb2
-rw-r--r--activesupport/lib/active_support/memoizable.rb35
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb2
-rw-r--r--activesupport/test/core_ext/array_ext_test.rb50
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb6
-rw-r--r--activesupport/test/memoizable_test.rb49
12 files changed, 215 insertions, 18 deletions
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index 983e7d0dac..8d3b136d80 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,5 +1,15 @@
*Edge*
+* Add Array#in_groups which splits or iterates over the array in specified number of groups. #579. [Adrian Mugnolo] Example:
+
+ a = (1..10).to_a
+ a.in_groups(3) #=> [[1, 2, 3, 4], [5, 6, 7, nil], [8, 9, 10, nil]]
+ a.in_groups(3, false) #=> [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
+
+* Fix TimeWithZone unmarshaling: coerce unmarshaled Time instances to utc, because Ruby's marshaling of Time instances doesn't respect the zone [Geoff Buesing]
+
+* Added Memoizable mixin for caching simple lazy loaded attributes [Josh Peek]
+
* Move the test related core_ext stuff out of core_ext so it's only loaded by the test helpers. [Michael Koziarski]
* Add Inflection rules for String#humanize. #535 [dcmanges]
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 1a8603e892..0526057b15 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -43,6 +43,7 @@ require 'active_support/ordered_hash'
require 'active_support/ordered_options'
require 'active_support/option_merger'
+require 'active_support/memoizable'
require 'active_support/string_inquirer'
require 'active_support/values/time_zone'
diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb
index 767acc4e07..df37afb053 100644
--- a/activesupport/lib/active_support/core_ext/array/grouping.rb
+++ b/activesupport/lib/active_support/core_ext/array/grouping.rb
@@ -4,8 +4,8 @@ module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module Array #:nodoc:
module Grouping
- # Iterates over the array in groups of size +number+, padding any remaining
- # slots with +fill_with+ unless it is +false+.
+ # Splits or iterates over the array in groups of size +number+,
+ # padding any remaining slots with +fill_with+ unless it is +false+.
#
# %w(1 2 3 4 5 6 7).in_groups_of(3) {|g| p g}
# ["1", "2", "3"]
@@ -39,6 +39,49 @@ module ActiveSupport #:nodoc:
end
end
+ # Splits or iterates over the array in +number+ of groups, padding any
+ # remaining slots with +fill_with+ unless it is +false+.
+ #
+ # %w(1 2 3 4 5 6 7 8 9 10).in_groups(3) {|g| p g}
+ # ["1", "2", "3", "4"]
+ # ["5", "6", "7", nil]
+ # ["8", "9", "10", nil]
+ #
+ # %w(1 2 3 4 5 6 7).in_groups(3, ' ') {|g| p g}
+ # ["1", "2", "3"]
+ # ["4", "5", " "]
+ # ["6", "7", " "]
+ #
+ # %w(1 2 3 4 5 6 7).in_groups(3, false) {|g| p g}
+ # ["1", "2", "3"]
+ # ["4", "5"]
+ # ["6", "7"]
+ def in_groups(number, fill_with = nil)
+ # size / number gives minor group size;
+ # size % number gives how many objects need extra accomodation;
+ # each group hold either division or division + 1 items.
+ division = size / number
+ modulo = size % number
+
+ # create a new array avoiding dup
+ groups = []
+ start = 0
+
+ number.times do |index|
+ length = division + (modulo > 0 && modulo > index ? 1 : 0)
+ padding = fill_with != false &&
+ modulo > 0 && length == division ? 1 : 0
+ groups << slice(start, length).concat([fill_with] * padding)
+ start += length
+ end
+
+ if block_given?
+ groups.each{|g| yield(g) }
+ else
+ groups
+ end
+ end
+
# Divides the array into one or more subarrays based on a delimiting +value+
# or the result of an optional block.
#
diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb
index 40bbebb7c4..45f3e4bf5c 100644
--- a/activesupport/lib/active_support/core_ext/module/introspection.rb
+++ b/activesupport/lib/active_support/core_ext/module/introspection.rb
@@ -1,4 +1,14 @@
class Module
+ # Returns the name of the module containing this one.
+ #
+ # p M::N.parent_name # => "M"
+ def parent_name
+ unless defined? @parent_name
+ @parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil
+ end
+ @parent_name
+ end
+
# Returns the module which contains this one according to its name.
#
# module M
@@ -16,8 +26,7 @@ class Module
# p Module.new.parent # => Object
#
def parent
- parent_name = name.split('::')[0..-2] * '::'
- parent_name.empty? ? Object : parent_name.constantize
+ parent_name ? parent_name.constantize : Object
end
# Returns all the parents of this module according to its name, ordered from
@@ -35,10 +44,12 @@ class Module
#
def parents
parents = []
- parts = name.split('::')[0..-2]
- until parts.empty?
- parents << (parts * '::').constantize
- parts.pop
+ if parent_name
+ parts = parent_name.split('::')
+ until parts.empty?
+ parents << (parts * '::').constantize
+ parts.pop
+ end
end
parents << Object unless parents.include? Object
parents
@@ -70,6 +81,6 @@ class Module
# Returns the names of the constants defined locally rather than the
# constants themselves. See <tt>local_constants</tt>.
def local_constant_names
- local_constants.map(&:to_s)
+ local_constants.map { |c| c.to_s }
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/instance_variables.rb b/activesupport/lib/active_support/core_ext/object/instance_variables.rb
index 9f1d4ed2aa..4ecaab3bbb 100644
--- a/activesupport/lib/active_support/core_ext/object/instance_variables.rb
+++ b/activesupport/lib/active_support/core_ext/object/instance_variables.rb
@@ -35,7 +35,7 @@ class Object
# C.new(0, 1).instance_variable_names # => ["@y", "@x"]
if RUBY_VERSION >= '1.9'
def instance_variable_names
- instance_variables.map(&:to_s)
+ instance_variables.map { |var| var.to_s }
end
else
alias_method :instance_variable_names, :instance_variables
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index d3d9ff9de4..2f3fa72bb4 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -387,7 +387,7 @@ module ActiveSupport #:nodoc:
ensure
# Remove the stack frames that we added.
if defined?(watch_frames) && ! watch_frames.blank?
- frame_ids = watch_frames.collect(&:object_id)
+ frame_ids = watch_frames.collect { |frame| frame.object_id }
constant_watch_stack.delete_if do |watch_frame|
frame_ids.include? watch_frame.object_id
end
@@ -437,7 +437,7 @@ module ActiveSupport #:nodoc:
protected
def log_call(*args)
if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER && log_activity
- arg_str = args.collect(&:inspect) * ', '
+ arg_str = args.collect { |arg| arg.inspect } * ', '
/in `([a-z_\?\!]+)'/ =~ caller(1).first
selector = $1 || '<unknown>'
log "called #{selector}(#{arg_str})"
diff --git a/activesupport/lib/active_support/json/encoders/date_time.rb b/activesupport/lib/active_support/json/encoders/date_time.rb
index d41c3e9786..a4a5efbfb1 100644
--- a/activesupport/lib/active_support/json/encoders/date_time.rb
+++ b/activesupport/lib/active_support/json/encoders/date_time.rb
@@ -8,7 +8,7 @@ class DateTime
if ActiveSupport.use_standard_json_time_format
xmlschema.inspect
else
- %("#{strftime("%Y/%m/%d %H:%M:%S %z")}")
+ strftime('"%Y/%m/%d %H:%M:%S %z"')
end
end
end
diff --git a/activesupport/lib/active_support/memoizable.rb b/activesupport/lib/active_support/memoizable.rb
new file mode 100644
index 0000000000..d06250171a
--- /dev/null
+++ b/activesupport/lib/active_support/memoizable.rb
@@ -0,0 +1,35 @@
+module ActiveSupport
+ module Memoizable
+ def self.included(base) #:nodoc:
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods
+ def memoize(symbol)
+ original_method = "_unmemoized_#{symbol}"
+ memoized_ivar = "@_memoized_#{symbol}"
+ raise "Already memoized #{symbol}" if instance_methods.map(&:to_s).include?(original_method)
+
+ alias_method original_method, symbol
+ class_eval <<-EOS, __FILE__, __LINE__
+ def #{symbol}
+ if defined? #{memoized_ivar}
+ #{memoized_ivar}
+ else
+ #{memoized_ivar} = #{original_method}
+ end
+ end
+ EOS
+ end
+ end
+
+ def freeze
+ methods.each do |method|
+ if m = method.to_s.match(/\A_unmemoized_(.*)/)
+ send(m[1]).freeze
+ end
+ end
+ super
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 88593eb92d..e85bfe9b2e 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -263,7 +263,7 @@ module ActiveSupport
end
def marshal_load(variables)
- initialize(variables[0], ::Time.send!(:get_zone, variables[1]), variables[2])
+ initialize(variables[0].utc, ::Time.send!(:get_zone, variables[1]), variables[2].utc)
end
# Ensure proxy class responds to all methods that underlying time instance responds to.
diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb
index 7563be44f8..62a1f61d53 100644
--- a/activesupport/test/core_ext/array_ext_test.rb
+++ b/activesupport/test/core_ext/array_ext_test.rb
@@ -99,7 +99,7 @@ class ArrayExtToSTests < Test::Unit::TestCase
end
class ArrayExtGroupingTests < Test::Unit::TestCase
- def test_group_by_with_perfect_fit
+ def test_in_groups_of_with_perfect_fit
groups = []
('a'..'i').to_a.in_groups_of(3) do |group|
groups << group
@@ -109,7 +109,7 @@ class ArrayExtGroupingTests < Test::Unit::TestCase
assert_equal [%w(a b c), %w(d e f), %w(g h i)], ('a'..'i').to_a.in_groups_of(3)
end
- def test_group_by_with_padding
+ def test_in_groups_of_with_padding
groups = []
('a'..'g').to_a.in_groups_of(3) do |group|
groups << group
@@ -118,7 +118,7 @@ class ArrayExtGroupingTests < Test::Unit::TestCase
assert_equal [%w(a b c), %w(d e f), ['g', nil, nil]], groups
end
- def test_group_by_pads_with_specified_values
+ def test_in_groups_of_pads_with_specified_values
groups = []
('a'..'g').to_a.in_groups_of(3, 'foo') do |group|
@@ -128,7 +128,7 @@ class ArrayExtGroupingTests < Test::Unit::TestCase
assert_equal [%w(a b c), %w(d e f), ['g', 'foo', 'foo']], groups
end
- def test_group_without_padding
+ def test_in_groups_of_without_padding
groups = []
('a'..'g').to_a.in_groups_of(3, false) do |group|
@@ -137,6 +137,48 @@ class ArrayExtGroupingTests < Test::Unit::TestCase
assert_equal [%w(a b c), %w(d e f), ['g']], groups
end
+
+ def test_in_groups_returned_array_size
+ array = (1..7).to_a
+
+ 1.upto(array.size + 1) do |number|
+ assert_equal number, array.in_groups(number).size
+ end
+ end
+
+ def test_in_groups_with_empty_array
+ assert_equal [[], [], []], [].in_groups(3)
+ end
+
+ def test_in_groups_with_block
+ array = (1..9).to_a
+ groups = []
+
+ array.in_groups(3) do |group|
+ groups << group
+ end
+
+ assert_equal array.in_groups(3), groups
+ end
+
+ def test_in_groups_with_perfect_fit
+ assert_equal [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
+ (1..9).to_a.in_groups(3)
+ end
+
+ def test_in_groups_with_padding
+ array = (1..7).to_a
+
+ assert_equal [[1, 2, 3], [4, 5, nil], [6, 7, nil]],
+ array.in_groups(3)
+ assert_equal [[1, 2, 3], [4, 5, 'foo'], [6, 7, 'foo']],
+ array.in_groups(3, 'foo')
+ end
+
+ def test_in_groups_without_padding
+ assert_equal [[1, 2, 3], [4, 5], [6, 7]],
+ (1..7).to_a.in_groups(3, false)
+ end
end
class ArraySplitTests < Test::Unit::TestCase
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index ac52a1be0b..dfe04485be 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -320,8 +320,11 @@ class TimeWithZoneTest < Test::Unit::TestCase
marshal_str = Marshal.dump(@twz)
mtime = Marshal.load(marshal_str)
assert_equal Time.utc(2000, 1, 1, 0), mtime.utc
+ assert mtime.utc.utc?
assert_equal ActiveSupport::TimeZone['Eastern Time (US & Canada)'], mtime.time_zone
assert_equal Time.utc(1999, 12, 31, 19), mtime.time
+ assert mtime.time.utc?
+ assert_equal @twz.inspect, mtime.inspect
end
end
@@ -331,8 +334,11 @@ class TimeWithZoneTest < Test::Unit::TestCase
marshal_str = Marshal.dump(twz)
mtime = Marshal.load(marshal_str)
assert_equal Time.utc(2000, 1, 1, 0), mtime.utc
+ assert mtime.utc.utc?
assert_equal 'America/New_York', mtime.time_zone.name
assert_equal Time.utc(1999, 12, 31, 19), mtime.time
+ assert mtime.time.utc?
+ assert_equal @twz.inspect, mtime.inspect
end
end
diff --git a/activesupport/test/memoizable_test.rb b/activesupport/test/memoizable_test.rb
new file mode 100644
index 0000000000..fc24a2942d
--- /dev/null
+++ b/activesupport/test/memoizable_test.rb
@@ -0,0 +1,49 @@
+require 'abstract_unit'
+
+uses_mocha 'Memoizable' do
+ class MemoizableTest < Test::Unit::TestCase
+ class Person
+ include ActiveSupport::Memoizable
+
+ def name
+ fetch_name_from_floppy
+ end
+ memoize :name
+
+ def age
+ nil
+ end
+ memoize :age
+
+ private
+ def fetch_name_from_floppy
+ "Josh"
+ end
+ end
+
+ def test_memoization
+ person = Person.new
+ assert_equal "Josh", person.name
+
+ person.expects(:fetch_name_from_floppy).never
+ 2.times { assert_equal "Josh", person.name }
+ end
+
+ def test_memoized_methods_are_frozen
+ person = Person.new
+ person.freeze
+ assert_equal "Josh", person.name
+ assert_equal true, person.name.frozen?
+ end
+
+ def test_memoization_frozen_with_nil_value
+ person = Person.new
+ person.freeze
+ assert_equal nil, person.age
+ end
+
+ def test_double_memoization
+ assert_raise(RuntimeError) { Person.memoize :name }
+ end
+ end
+end