activesupport-refinements 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +6 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +32 -0
  5. data/Rakefile +1 -0
  6. data/activesupport-refinements.gemspec +21 -0
  7. data/lib/active_support/refinements/core_ext/array.rb +7 -0
  8. data/lib/active_support/refinements/core_ext/array/access.rb +56 -0
  9. data/lib/active_support/refinements/core_ext/array/conversions.rb +224 -0
  10. data/lib/active_support/refinements/core_ext/array/extract_options.rb +31 -0
  11. data/lib/active_support/refinements/core_ext/array/grouping.rb +101 -0
  12. data/lib/active_support/refinements/core_ext/array/prepend_and_append.rb +9 -0
  13. data/lib/active_support/refinements/core_ext/array/uniq_by.rb +21 -0
  14. data/lib/active_support/refinements/core_ext/array/wrap.rb +48 -0
  15. data/lib/active_support/refinements/core_ext/benchmark.rb +7 -0
  16. data/lib/active_support/refinements/core_ext/big_decimal.rb +1 -0
  17. data/lib/active_support/refinements/core_ext/big_decimal/conversions.rb +32 -0
  18. data/lib/active_support/refinements/core_ext/class.rb +4 -0
  19. data/lib/active_support/refinements/core_ext/class/attribute.rb +119 -0
  20. data/lib/active_support/refinements/core_ext/class/attribute_accessors.rb +172 -0
  21. data/lib/active_support/refinements/core_ext/class/delegating_attributes.rb +42 -0
  22. data/lib/active_support/refinements/core_ext/class/subclasses.rb +44 -0
  23. data/lib/active_support/refinements/core_ext/date.rb +5 -0
  24. data/lib/active_support/refinements/core_ext/date/acts_like.rb +10 -0
  25. data/lib/active_support/refinements/core_ext/date/calculations.rb +123 -0
  26. data/lib/active_support/refinements/core_ext/date/conversions.rb +86 -0
  27. data/lib/active_support/refinements/core_ext/date/zones.rb +17 -0
  28. data/lib/active_support/refinements/core_ext/date_and_time/calculations.rb +232 -0
  29. data/lib/active_support/refinements/core_ext/date_time.rb +4 -0
  30. data/lib/active_support/refinements/core_ext/date_time/acts_like.rb +15 -0
  31. data/lib/active_support/refinements/core_ext/date_time/calculations.rb +143 -0
  32. data/lib/active_support/refinements/core_ext/date_time/conversions.rb +93 -0
  33. data/lib/active_support/refinements/core_ext/date_time/zones.rb +26 -0
  34. data/lib/active_support/refinements/core_ext/enumerable.rb +82 -0
  35. data/lib/active_support/refinements/core_ext/exception.rb +5 -0
  36. data/lib/active_support/refinements/core_ext/file.rb +1 -0
  37. data/lib/active_support/refinements/core_ext/file/atomic.rb +60 -0
  38. data/lib/active_support/refinements/core_ext/hash.rb +8 -0
  39. data/lib/active_support/refinements/core_ext/hash/conversions.rb +161 -0
  40. data/lib/active_support/refinements/core_ext/hash/deep_merge.rb +29 -0
  41. data/lib/active_support/refinements/core_ext/hash/diff.rb +15 -0
  42. data/lib/active_support/refinements/core_ext/hash/except.rb +17 -0
  43. data/lib/active_support/refinements/core_ext/hash/indifferent_access.rb +24 -0
  44. data/lib/active_support/refinements/core_ext/hash/keys.rb +140 -0
  45. data/lib/active_support/refinements/core_ext/hash/reverse_merge.rb +24 -0
  46. data/lib/active_support/refinements/core_ext/hash/slice.rb +42 -0
  47. data/lib/active_support/refinements/core_ext/integer.rb +3 -0
  48. data/lib/active_support/refinements/core_ext/integer/inflections.rb +31 -0
  49. data/lib/active_support/refinements/core_ext/integer/multiple.rb +12 -0
  50. data/lib/active_support/refinements/core_ext/integer/time.rb +43 -0
  51. data/lib/active_support/refinements/core_ext/kernel.rb +4 -0
  52. data/lib/active_support/refinements/core_ext/kernel/agnostics.rb +13 -0
  53. data/lib/active_support/refinements/core_ext/kernel/debugger.rb +12 -0
  54. data/lib/active_support/refinements/core_ext/kernel/reporting.rb +97 -0
  55. data/lib/active_support/refinements/core_ext/kernel/singleton_class.rb +8 -0
  56. data/lib/active_support/refinements/core_ext/load_error.rb +27 -0
  57. data/lib/active_support/refinements/core_ext/logger.rb +86 -0
  58. data/lib/active_support/refinements/core_ext/module.rb +10 -0
  59. data/lib/active_support/refinements/core_ext/module/aliasing.rb +69 -0
  60. data/lib/active_support/refinements/core_ext/module/anonymous.rb +21 -0
  61. data/lib/active_support/refinements/core_ext/module/attr_internal.rb +40 -0
  62. data/lib/active_support/refinements/core_ext/module/attribute_accessors.rb +68 -0
  63. data/lib/active_support/refinements/core_ext/module/delegation.rb +172 -0
  64. data/lib/active_support/refinements/core_ext/module/deprecation.rb +27 -0
  65. data/lib/active_support/refinements/core_ext/module/introspection.rb +80 -0
  66. data/lib/active_support/refinements/core_ext/module/qualified_const.rb +54 -0
  67. data/lib/active_support/refinements/core_ext/module/reachable.rb +10 -0
  68. data/lib/active_support/refinements/core_ext/module/remove_method.rb +14 -0
  69. data/lib/active_support/refinements/core_ext/name_error.rb +20 -0
  70. data/lib/active_support/refinements/core_ext/numeric.rb +3 -0
  71. data/lib/active_support/refinements/core_ext/numeric/bytes.rb +46 -0
  72. data/lib/active_support/refinements/core_ext/numeric/conversions.rb +137 -0
  73. data/lib/active_support/refinements/core_ext/numeric/time.rb +81 -0
  74. data/lib/active_support/refinements/core_ext/object.rb +14 -0
  75. data/lib/active_support/refinements/core_ext/object/acts_like.rb +12 -0
  76. data/lib/active_support/refinements/core_ext/object/blank.rb +107 -0
  77. data/lib/active_support/refinements/core_ext/object/conversions.rb +4 -0
  78. data/lib/active_support/refinements/core_ext/object/deep_dup.rb +48 -0
  79. data/lib/active_support/refinements/core_ext/object/duplicable.rb +92 -0
  80. data/lib/active_support/refinements/core_ext/object/inclusion.rb +27 -0
  81. data/lib/active_support/refinements/core_ext/object/instance_variables.rb +30 -0
  82. data/lib/active_support/refinements/core_ext/object/to_json.rb +27 -0
  83. data/lib/active_support/refinements/core_ext/object/to_param.rb +60 -0
  84. data/lib/active_support/refinements/core_ext/object/to_query.rb +29 -0
  85. data/lib/active_support/refinements/core_ext/object/try.rb +72 -0
  86. data/lib/active_support/refinements/core_ext/object/with_options.rb +44 -0
  87. data/lib/active_support/refinements/core_ext/proc.rb +19 -0
  88. data/lib/active_support/refinements/core_ext/range.rb +3 -0
  89. data/lib/active_support/refinements/core_ext/range/conversions.rb +21 -0
  90. data/lib/active_support/refinements/core_ext/range/include_range.rb +23 -0
  91. data/lib/active_support/refinements/core_ext/range/overlaps.rb +10 -0
  92. data/lib/active_support/refinements/core_ext/regexp.rb +7 -0
  93. data/lib/active_support/refinements/core_ext/string.rb +13 -0
  94. data/lib/active_support/refinements/core_ext/string/access.rb +106 -0
  95. data/lib/active_support/refinements/core_ext/string/behavior.rb +8 -0
  96. data/lib/active_support/refinements/core_ext/string/conversions.rb +60 -0
  97. data/lib/active_support/refinements/core_ext/string/encoding.rb +10 -0
  98. data/lib/active_support/refinements/core_ext/string/exclude.rb +13 -0
  99. data/lib/active_support/refinements/core_ext/string/filters.rb +54 -0
  100. data/lib/active_support/refinements/core_ext/string/indent.rb +45 -0
  101. data/lib/active_support/refinements/core_ext/string/inflections.rb +214 -0
  102. data/lib/active_support/refinements/core_ext/string/inquiry.rb +15 -0
  103. data/lib/active_support/refinements/core_ext/string/multibyte.rb +58 -0
  104. data/lib/active_support/refinements/core_ext/string/output_safety.rb +194 -0
  105. data/lib/active_support/refinements/core_ext/string/starts_ends_with.rb +6 -0
  106. data/lib/active_support/refinements/core_ext/string/strip.rb +28 -0
  107. data/lib/active_support/refinements/core_ext/string/xchar.rb +18 -0
  108. data/lib/active_support/refinements/core_ext/time.rb +5 -0
  109. data/lib/active_support/refinements/core_ext/time/acts_like.rb +10 -0
  110. data/lib/active_support/refinements/core_ext/time/calculations.rb +251 -0
  111. data/lib/active_support/refinements/core_ext/time/conversions.rb +65 -0
  112. data/lib/active_support/refinements/core_ext/time/marshal.rb +30 -0
  113. data/lib/active_support/refinements/core_ext/time/zones.rb +98 -0
  114. data/lib/active_support/refinements/core_ext/uri.rb +28 -0
  115. data/lib/activesupport-refinements.rb +9 -0
  116. data/lib/activesupport-refinements/version.rb +5 -0
  117. data/refine_core_ext.rb +45 -0
  118. data/spec/hwia_spec.rb +15 -0
  119. data/spec/try_spec.rb +18 -0
  120. metadata +182 -0
@@ -0,0 +1,9 @@
1
+ module ArrayExt; end; module ArrayExt::PrependAndAppend
2
+ refine Array do
3
+ # The human way of thinking about adding stuff to the end of a list is with append
4
+ # alias_method :append, :<<
5
+
6
+ # The human way of thinking about adding stuff to the beginning of a list is with prepend
7
+ # alias_method :prepend, :unshift
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ module ArrayExt; end; module ArrayExt::UniqBy
2
+ refine Array do
3
+ # *DEPRECATED*: Use +Array#uniq+ instead.
4
+ #
5
+ # Returns a unique array based on the criteria in the block.
6
+ #
7
+ # [1, 2, 3, 4].uniq_by { |i| i.odd? } # => [1, 2]
8
+ def uniq_by(&block)
9
+ ActiveSupport::Deprecation.warn 'uniq_by is deprecated. Use Array#uniq instead', caller
10
+ uniq(&block)
11
+ end
12
+
13
+ # *DEPRECATED*: Use +Array#uniq!+ instead.
14
+ #
15
+ # Same as +uniq_by+, but modifies +self+.
16
+ def uniq_by!(&block)
17
+ ActiveSupport::Deprecation.warn 'uniq_by! is deprecated. Use Array#uniq! instead', caller
18
+ uniq!(&block)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,48 @@
1
+ module ArrayExt; end; module ArrayExt::Wrap
2
+ refine Array do
3
+ # Wraps its argument in an array unless it is already an array (or array-like).
4
+ #
5
+ # Specifically:
6
+ #
7
+ # * If the argument is +nil+ an empty list is returned.
8
+ # * Otherwise, if the argument responds to +to_ary+ it is invoked, and its result returned.
9
+ # * Otherwise, returns an array with the argument as its single element.
10
+ #
11
+ # Array.wrap(nil) # => []
12
+ # Array.wrap([1, 2, 3]) # => [1, 2, 3]
13
+ # Array.wrap(0) # => [0]
14
+ #
15
+ # This method is similar in purpose to <tt>Kernel#Array</tt>, but there are some differences:
16
+ #
17
+ # * If the argument responds to +to_ary+ the method is invoked. <tt>Kernel#Array</tt>
18
+ # moves on to try +to_a+ if the returned value is +nil+, but <tt>Array.wrap</tt> returns
19
+ # such a +nil+ right away.
20
+ # * If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, <tt>Kernel#Array</tt>
21
+ # raises an exception, while <tt>Array.wrap</tt> does not, it just returns the value.
22
+ # * It does not call +to_a+ on the argument, though special-cases +nil+ to return an empty array.
23
+ #
24
+ # The last point is particularly worth comparing for some enumerables:
25
+ #
26
+ # Array(foo: :bar) # => [[:foo, :bar]]
27
+ # Array.wrap(foo: :bar) # => [{foo: :bar}]
28
+ #
29
+ # There's also a related idiom that uses the splat operator:
30
+ #
31
+ # [*object]
32
+ #
33
+ # which for +nil+ returns <tt>[nil]</tt> (Ruby 1.8.7) or <tt>[]</tt> (Ruby
34
+ # 1.9), and calls to <tt>Array(object)</tt> otherwise.
35
+ #
36
+ # Thus, in this case the behavior may be different for +nil+, and the differences with
37
+ # <tt>Kernel#Array</tt> explained above apply to the rest of <tt>object</tt>s.
38
+ def self.wrap(object)
39
+ if object.nil?
40
+ []
41
+ elsif object.respond_to?(:to_ary)
42
+ object.to_ary || [object]
43
+ else
44
+ [object]
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,7 @@
1
+ require 'benchmark'
2
+
3
+ class << Benchmark
4
+ def ms
5
+ 1000 * realtime { yield }
6
+ end
7
+ end
@@ -0,0 +1 @@
1
+ require 'active_support/refinements/core_ext/big_decimal/conversions'
@@ -0,0 +1,32 @@
1
+ module BigDecimalExt; end; module BigDecimalExt::Conversions
2
+ require 'bigdecimal'
3
+ require 'yaml'
4
+
5
+ refine BigDecimal do
6
+ YAML_MAPPING = { 'Infinity' => '.Inf', '-Infinity' => '-.Inf', 'NaN' => '.NaN' }
7
+
8
+ def encode_with(coder)
9
+ string = to_s
10
+ coder.represent_scalar(nil, YAML_MAPPING[string] || string)
11
+ end
12
+
13
+ # Backport this method if it doesn't exist
14
+ unless method_defined?(:to_d)
15
+ def to_d
16
+ self
17
+ end
18
+ end
19
+
20
+ DEFAULT_STRING_FORMAT = 'F'
21
+ def to_formatted_s(*args)
22
+ if args[0].is_a?(Symbol)
23
+ super
24
+ else
25
+ format = args[0] || DEFAULT_STRING_FORMAT
26
+ _original_to_s(format)
27
+ end
28
+ end
29
+ # alias_method :_original_to_s, :to_s
30
+ # alias_method :to_s, :to_formatted_s
31
+ end
32
+ end
@@ -0,0 +1,4 @@
1
+ require 'active_support/refinements/core_ext/class/attribute'
2
+ require 'active_support/refinements/core_ext/class/attribute_accessors'
3
+ require 'active_support/refinements/core_ext/class/delegating_attributes'
4
+ require 'active_support/refinements/core_ext/class/subclasses'
@@ -0,0 +1,119 @@
1
+ module ClassExt; end; module ClassExt::Attribute
2
+ require 'active_support/refinements/core_ext/kernel/singleton_class'
3
+ require 'active_support/refinements/core_ext/module/remove_method'
4
+ require 'active_support/refinements/core_ext/array/extract_options'
5
+
6
+ refine Class do
7
+ # Declare a class-level attribute whose value is inheritable by subclasses.
8
+ # Subclasses can change their own value and it will not impact parent class.
9
+ #
10
+ # class Base
11
+ # class_attribute :setting
12
+ # end
13
+ #
14
+ # class Subclass < Base
15
+ # end
16
+ #
17
+ # Base.setting = true
18
+ # Subclass.setting # => true
19
+ # Subclass.setting = false
20
+ # Subclass.setting # => false
21
+ # Base.setting # => true
22
+ #
23
+ # In the above case as long as Subclass does not assign a value to setting
24
+ # by performing <tt>Subclass.setting = _something_ </tt>, <tt>Subclass.setting</tt>
25
+ # would read value assigned to parent class. Once Subclass assigns a value then
26
+ # the value assigned by Subclass would be returned.
27
+ #
28
+ # This matches normal Ruby method inheritance: think of writing an attribute
29
+ # on a subclass as overriding the reader method. However, you need to be aware
30
+ # when using +class_attribute+ with mutable structures as +Array+ or +Hash+.
31
+ # In such cases, you don't want to do changes in places but use setters:
32
+ #
33
+ # Base.setting = []
34
+ # Base.setting # => []
35
+ # Subclass.setting # => []
36
+ #
37
+ # # Appending in child changes both parent and child because it is the same object:
38
+ # Subclass.setting << :foo
39
+ # Base.setting # => [:foo]
40
+ # Subclass.setting # => [:foo]
41
+ #
42
+ # # Use setters to not propagate changes:
43
+ # Base.setting = []
44
+ # Subclass.setting += [:foo]
45
+ # Base.setting # => []
46
+ # Subclass.setting # => [:foo]
47
+ #
48
+ # For convenience, a query method is defined as well:
49
+ #
50
+ # Subclass.setting? # => false
51
+ #
52
+ # Instances may overwrite the class value in the same way:
53
+ #
54
+ # Base.setting = true
55
+ # object = Base.new
56
+ # object.setting # => true
57
+ # object.setting = false
58
+ # object.setting # => false
59
+ # Base.setting # => true
60
+ #
61
+ # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
62
+ #
63
+ # object.setting # => NoMethodError
64
+ # object.setting? # => NoMethodError
65
+ #
66
+ # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
67
+ #
68
+ # object.setting = false # => NoMethodError
69
+ #
70
+ # To opt out of both instance methods, pass <tt>instance_accessor: false</tt>.
71
+ def class_attribute(*attrs)
72
+ options = attrs.extract_options!
73
+ instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
74
+ instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true)
75
+
76
+ attrs.each do |name|
77
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
78
+ def self.#{name}() nil end
79
+ def self.#{name}?() !!#{name} end
80
+
81
+ def self.#{name}=(val)
82
+ singleton_class.class_eval do
83
+ remove_possible_method(:#{name})
84
+ define_method(:#{name}) { val }
85
+ end
86
+
87
+ if singleton_class?
88
+ class_eval do
89
+ remove_possible_method(:#{name})
90
+ def #{name}
91
+ defined?(@#{name}) ? @#{name} : singleton_class.#{name}
92
+ end
93
+ end
94
+ end
95
+ val
96
+ end
97
+
98
+ if instance_reader
99
+ remove_possible_method :#{name}
100
+ def #{name}
101
+ defined?(@#{name}) ? @#{name} : self.class.#{name}
102
+ end
103
+
104
+ def #{name}?
105
+ !!#{name}
106
+ end
107
+ end
108
+ RUBY
109
+
110
+ attr_writer name if instance_writer
111
+ end
112
+ end
113
+
114
+ private
115
+ def singleton_class?
116
+ ancestors.first != self
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,172 @@
1
+ module ClassExt; end; module ClassExt::AttributeAccessors
2
+ require 'active_support/refinements/core_ext/array/extract_options'
3
+
4
+ # Extends the class object with class and instance accessors for class attributes,
5
+ # just like the native attr* accessors for instance attributes.
6
+ refine Class do
7
+ # Defines a class attribute if it's not defined and creates a reader method that
8
+ # returns the attribute value.
9
+ #
10
+ # class Person
11
+ # cattr_reader :hair_colors
12
+ # end
13
+ #
14
+ # Person.class_variable_set("@@hair_colors", [:brown, :black])
15
+ # Person.hair_colors # => [:brown, :black]
16
+ # Person.new.hair_colors # => [:brown, :black]
17
+ #
18
+ # The attribute name must be a valid method name in Ruby.
19
+ #
20
+ # class Person
21
+ # cattr_reader :"1_Badname "
22
+ # end
23
+ # # => NameError: invalid attribute name
24
+ #
25
+ # If you want to opt out the instance reader method, you can pass <tt>instance_reader: false</tt>
26
+ # or <tt>instance_accessor: false</tt>.
27
+ #
28
+ # class Person
29
+ # cattr_reader :hair_colors, instance_reader: false
30
+ # end
31
+ #
32
+ # Person.new.hair_colors # => NoMethodError
33
+ def cattr_reader(*syms)
34
+ options = syms.extract_options!
35
+ syms.each do |sym|
36
+ raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
37
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
38
+ unless defined? @@#{sym}
39
+ @@#{sym} = nil
40
+ end
41
+
42
+ def self.#{sym}
43
+ @@#{sym}
44
+ end
45
+ EOS
46
+
47
+ unless options[:instance_reader] == false || options[:instance_accessor] == false
48
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
49
+ def #{sym}
50
+ @@#{sym}
51
+ end
52
+ EOS
53
+ end
54
+ end
55
+ end
56
+
57
+ # Defines a class attribute if it's not defined and creates a writer method to allow
58
+ # assignment to the attribute.
59
+ #
60
+ # class Person
61
+ # cattr_writer :hair_colors
62
+ # end
63
+ #
64
+ # Person.hair_colors = [:brown, :black]
65
+ # Person.class_variable_get("@@hair_colors") # => [:brown, :black]
66
+ # Person.new.hair_colors = [:blonde, :red]
67
+ # Person.class_variable_get("@@hair_colors") # => [:blonde, :red]
68
+ #
69
+ # The attribute name must be a valid method name in Ruby.
70
+ #
71
+ # class Person
72
+ # cattr_writer :"1_Badname "
73
+ # end
74
+ # # => NameError: invalid attribute name
75
+ #
76
+ # If you want to opt out the instance writer method, pass <tt>instance_writer: false</tt>
77
+ # or <tt>instance_accessor: false</tt>.
78
+ #
79
+ # class Person
80
+ # cattr_writer :hair_colors, instance_writer: false
81
+ # end
82
+ #
83
+ # Person.new.hair_colors = [:blonde, :red] # => NoMethodError
84
+ #
85
+ # Also, you can pass a block to set up the attribute with a default value.
86
+ #
87
+ # class Person
88
+ # cattr_writer :hair_colors do
89
+ # [:brown, :black, :blonde, :red]
90
+ # end
91
+ # end
92
+ #
93
+ # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
94
+ def cattr_writer(*syms)
95
+ options = syms.extract_options!
96
+ syms.each do |sym|
97
+ raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
98
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
99
+ unless defined? @@#{sym}
100
+ @@#{sym} = nil
101
+ end
102
+
103
+ def self.#{sym}=(obj)
104
+ @@#{sym} = obj
105
+ end
106
+ EOS
107
+
108
+ unless options[:instance_writer] == false || options[:instance_accessor] == false
109
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
110
+ def #{sym}=(obj)
111
+ @@#{sym} = obj
112
+ end
113
+ EOS
114
+ end
115
+ send("#{sym}=", yield) if block_given?
116
+ end
117
+ end
118
+
119
+ # Defines both class and instance accessors for class attributes.
120
+ #
121
+ # class Person
122
+ # cattr_accessor :hair_colors
123
+ # end
124
+ #
125
+ # Person.hair_colors = [:brown, :black, :blonde, :red]
126
+ # Person.hair_colors # => [:brown, :black, :blonde, :red]
127
+ # Person.new.hair_colors # => [:brown, :black, :blonde, :red]
128
+ #
129
+ # If a subclass changes the value then that would also change the value for
130
+ # parent class. Similarly if parent class changes the value then that would
131
+ # change the value of subclasses too.
132
+ #
133
+ # class Male < Person
134
+ # end
135
+ #
136
+ # Male.hair_colors << :blue
137
+ # Person.hair_colors # => [:brown, :black, :blonde, :red, :blue]
138
+ #
139
+ # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
140
+ # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
141
+ #
142
+ # class Person
143
+ # cattr_accessor :hair_colors, instance_writer: false, instance_reader: false
144
+ # end
145
+ #
146
+ # Person.new.hair_colors = [:brown] # => NoMethodError
147
+ # Person.new.hair_colors # => NoMethodError
148
+ #
149
+ # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
150
+ #
151
+ # class Person
152
+ # cattr_accessor :hair_colors, instance_accessor: false
153
+ # end
154
+ #
155
+ # Person.new.hair_colors = [:brown] # => NoMethodError
156
+ # Person.new.hair_colors # => NoMethodError
157
+ #
158
+ # Also you can pass a block to set up the attribute with a default value.
159
+ #
160
+ # class Person
161
+ # cattr_accessor :hair_colors do
162
+ # [:brown, :black, :blonde, :red]
163
+ # end
164
+ # end
165
+ #
166
+ # Person.class_variable_get("@@hair_colors") #=> [:brown, :black, :blonde, :red]
167
+ def cattr_accessor(*syms, &blk)
168
+ cattr_reader(*syms)
169
+ cattr_writer(*syms, &blk)
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,42 @@
1
+ module ClassExt; end; module ClassExt::DelegatingAttributes
2
+ require 'active_support/refinements/core_ext/kernel/singleton_class'
3
+ require 'active_support/refinements/core_ext/module/remove_method'
4
+
5
+ refine Class do
6
+ def superclass_delegating_accessor(name, options = {})
7
+ # Create private _name and _name= methods that can still be used if the public
8
+ # methods are overridden. This allows
9
+ _superclass_delegating_accessor("_#{name}")
10
+
11
+ # Generate the public methods name, name=, and name?
12
+ # These methods dispatch to the private _name, and _name= methods, making them
13
+ # overridable
14
+ singleton_class.send(:define_method, name) { send("_#{name}") }
15
+ singleton_class.send(:define_method, "#{name}?") { !!send("_#{name}") }
16
+ singleton_class.send(:define_method, "#{name}=") { |value| send("_#{name}=", value) }
17
+
18
+ # If an instance_reader is needed, generate methods for name and name= on the
19
+ # class itself, so instances will be able to see them
20
+ define_method(name) { send("_#{name}") } if options[:instance_reader] != false
21
+ define_method("#{name}?") { !!send("#{name}") } if options[:instance_reader] != false
22
+ end
23
+
24
+ private
25
+ # Take the object being set and store it in a method. This gives us automatic
26
+ # inheritance behavior, without having to store the object in an instance
27
+ # variable and look up the superclass chain manually.
28
+ def _stash_object_in_method(object, method, instance_reader = true)
29
+ singleton_class.remove_possible_method(method)
30
+ singleton_class.send(:define_method, method) { object }
31
+ remove_possible_method(method)
32
+ define_method(method) { object } if instance_reader
33
+ end
34
+
35
+ def _superclass_delegating_accessor(name, options = {})
36
+ singleton_class.send(:define_method, "#{name}=") do |value|
37
+ _stash_object_in_method(value, name, options[:instance_reader] != false)
38
+ end
39
+ send("#{name}=", nil)
40
+ end
41
+ end
42
+ end