diff options
Diffstat (limited to 'activesupport')
-rw-r--r-- | activesupport/CHANGELOG | 10 | ||||
-rw-r--r-- | activesupport/lib/active_support.rb | 1 | ||||
-rw-r--r-- | activesupport/lib/active_support/core_ext/array/grouping.rb | 47 | ||||
-rw-r--r-- | activesupport/lib/active_support/core_ext/module/introspection.rb | 25 | ||||
-rw-r--r-- | activesupport/lib/active_support/core_ext/object/instance_variables.rb | 2 | ||||
-rw-r--r-- | activesupport/lib/active_support/dependencies.rb | 4 | ||||
-rw-r--r-- | activesupport/lib/active_support/json/encoders/date_time.rb | 2 | ||||
-rw-r--r-- | activesupport/lib/active_support/memoizable.rb | 35 | ||||
-rw-r--r-- | activesupport/lib/active_support/time_with_zone.rb | 2 | ||||
-rw-r--r-- | activesupport/test/core_ext/array_ext_test.rb | 50 | ||||
-rw-r--r-- | activesupport/test/core_ext/time_with_zone_test.rb | 6 | ||||
-rw-r--r-- | activesupport/test/memoizable_test.rb | 49 |
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 |