attr_plus 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/LICENSE ADDED
@@ -0,0 +1,17 @@
1
+ Copyright (c) 2011 Joshua Hawxwell
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, there are
9
+ no conditions to fulfill.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
12
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
13
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
14
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
15
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
16
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
17
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,81 @@
1
+ # ClassAttr
2
+
3
+ Adds `#class_attr_accessor` (and reader/writer) and `#inheritable_class_attr_accessor` (and
4
+ reader/writer of course) to Class.
5
+
6
+ class Polygon
7
+ class_attr_accessor :sides
8
+ end
9
+
10
+ Polygon.sides = 5
11
+ Polygon.sides #=> 5
12
+
13
+ class Square < Polygon
14
+ end
15
+
16
+ Square.sides #=> nil
17
+
18
+
19
+ class InheritablePolygon
20
+ inheritable_class_attr_accessor :sides
21
+ end
22
+
23
+ Polygon.sides = 4
24
+
25
+ class NewSquare < Polygon
26
+ end
27
+
28
+ Square.sides #=> 4
29
+
30
+ You can provide default values using a hash with :default set for the last value, or if just
31
+ creating one accessor/reader/writer add `=> defaultvalue` to the end, this example should
32
+ make it more clear:
33
+
34
+ class Person
35
+ class_attr_accessor :name => 'John Doe'
36
+ inheritable_class_attr_accessor :arms, :legs, :default => 2
37
+ inheritable_class_attr_accessor :fingers, :toes, :default => 5
38
+ end
39
+
40
+ class Agent < Person
41
+ @name = "James Bond"
42
+ end
43
+
44
+ Person.name #=> "John Doe"
45
+ Person.arms #=> 2
46
+ Person.toes #=> 5
47
+ Agent.name #=> "James Bond"
48
+ Agent.legs #=> 2
49
+
50
+
51
+ ## Install
52
+
53
+ (sudo) gem install class_attr
54
+
55
+
56
+ ## Use
57
+
58
+ require 'class_attr'
59
+
60
+ Or, as in some cases this may be overkill, copy the parts you need over (you will need to modify it,
61
+ but that should be easy to work out.)
62
+
63
+
64
+ ### Important!
65
+
66
+ If in a class you define the `self.inherited` method, make sure to call super at the end (or beginning)
67
+ otherwise default values will not be set for the subclass. But I'm guessing you already called super
68
+ anyway in a method like that.
69
+
70
+
71
+ ## Thanks
72
+
73
+ - <http://railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/>
74
+ For originally demystifying the class instance variable thing.
75
+ - <http://www.raulparolari.com/Rails/class_inheritable> For ideas on how to implement
76
+ class level attribute accessors which inherit values.
77
+
78
+
79
+ ## Copyright
80
+
81
+ Copyright (c) 2010 Joshua Hawxwell. See LICENSE for details.
@@ -0,0 +1,3 @@
1
+ require 'attr_plus/class'
2
+ require 'attr_plus/instance'
3
+ require 'attr_plus/module'
@@ -0,0 +1,222 @@
1
+ require 'attr_plus/ext'
2
+
3
+ class Class
4
+
5
+ # Defines a method that allows you to read an instance variable set at the
6
+ # class level. Also defines an _instance_ method that reads the same thing.
7
+ # Comparable to #attr_reader for the class level.
8
+ #
9
+ # So in summary defines: .var (which gets @var) and
10
+ # #var (which gets self.class.var)
11
+ #
12
+ # @example
13
+ #
14
+ # class Polygon
15
+ # class_attr_reader :sides
16
+ #
17
+ # def self.is_a=(shape)
18
+ # case shape
19
+ # when 'triangle' then @sides = 3
20
+ # when 'square' then @sides = 4
21
+ # end
22
+ # end
23
+ # end
24
+ #
25
+ # Polygon.sides #=> nil
26
+ # Polygon.is_a 'triangle'
27
+ # Polygon.sides #=> 3
28
+ # t = Polygon.new
29
+ # t.sides #=> 3
30
+ #
31
+ def class_attr_reader(*args)
32
+ names, default = separate_arguments_and_set_defaults(args)
33
+ names.each do |name|
34
+ class_eval <<-EOS
35
+ def self.#{name}
36
+ @#{name}
37
+ end
38
+
39
+ def #{name}
40
+ self.class.send(:#{name})
41
+ end
42
+ EOS
43
+ self.instance_variable_set("@#{name}", (default.dup rescue default))
44
+ end
45
+ end
46
+
47
+ # Defines a method for to write to an instance varaible set at the class
48
+ # level. Comparable to #attr_writer for the class level.
49
+ #
50
+ # So in summary defines: .var= (which sets @var)
51
+ #
52
+ # @example
53
+ #
54
+ # class Polygon
55
+ # class_attr_writer :sides
56
+ #
57
+ # def self.rectangle?; @sides == 4; end
58
+ # def self.triangle?; @sides == 3; end
59
+ # end
60
+ #
61
+ # Polygon.rectangle? #=> false
62
+ # Polygon.sides = 4
63
+ # Polygon.rectangle? #=> true
64
+ # Polygon.sides = 3
65
+ # Polygon.triangle? #=> true
66
+ #
67
+ def class_attr_writer(*args)
68
+ names, default = separate_arguments_and_set_defaults(args)
69
+ names.each do |name|
70
+ class_eval <<-EOS
71
+ def self.#{name}=(obj)
72
+ @#{name} = obj
73
+ end
74
+ EOS
75
+ end
76
+ end
77
+
78
+ # Executes #class_attr_reader and #class_attr_writer.
79
+ #
80
+ # @see #class_attr_reader
81
+ # @see #class_attr_writer
82
+ #
83
+ # @example
84
+ #
85
+ # class Polygon
86
+ # class_attr_accessor :sides
87
+ # end
88
+ #
89
+ # Polygon.sides #=> nil
90
+ # Polygon.sides = 5
91
+ # Polygon.sides #=> 5
92
+ # pentagon = Polygon.new
93
+ # pentagon.sides #=> 5
94
+ #
95
+ def class_attr_accessor(*names)
96
+ class_attr_reader(*names)
97
+ class_attr_writer(*names)
98
+ end
99
+
100
+ # Creates a class and instance method to read the class level variable(s)
101
+ # with the name(s) provided. If no value has been set it attempts to use
102
+ # the value of the superclass.
103
+ #
104
+ # @see #class_attr_reader
105
+ #
106
+ def inheritable_class_attr_reader(*args)
107
+ names, default = separate_arguments_and_set_defaults(args)
108
+ names.each do |name|
109
+ class_eval <<-EOS
110
+ def self.#{name}
111
+ if @#{name}
112
+ @#{name}
113
+ elsif superclass.respond_to? :#{name}
114
+ @#{name} ||= superclass.#{name}
115
+ end
116
+ end
117
+
118
+ def #{name}
119
+ self.class.send(:#{name})
120
+ end
121
+ EOS
122
+ self.instance_variable_set("@#{name}", (default.dup rescue default))
123
+ end
124
+ end
125
+
126
+ # The same as #class_attr_writer.
127
+ def inheritable_class_attr_writer(*args)
128
+ class_attr_writer(*args)
129
+ end
130
+
131
+ # Executes #inheritable_class_attr_reader and #inheritable_class_attr_writer.
132
+ #
133
+ # @see #inheritable_class_attr_reader
134
+ # @see #inheritable_class_attr_writer
135
+ #
136
+ # @example
137
+ #
138
+ # class Polygon
139
+ # inheritable_class_attr_accessor :sides, :angles
140
+ # self.angles = [90]
141
+ # end
142
+ #
143
+ # class Triangle < Polygon
144
+ # self.sides = 3
145
+ # self.angles = [100, 35, 45]
146
+ # end
147
+ #
148
+ # class Rectangle < Polygon
149
+ # self.sides = 4
150
+ # self.angles << 60 << 100 << 110
151
+ # end
152
+ #
153
+ # class Square < Rectangle
154
+ # # should get 4 sides from rectange
155
+ # self.angles = [90] * 4
156
+ # end
157
+ #
158
+ # Polygon.sides #=> nil
159
+ # Triangle.sides #=> 3
160
+ # Rectangle.sides #=> 4
161
+ # Rectangle.angles #=> [90, 60, 100, 110]
162
+ # Square.sides #=> 4
163
+ # Square.angles #=> [90, 90, 90, 90]
164
+ #
165
+ def inheritable_class_attr_accessor(*names)
166
+ inheritable_class_attr_reader(*names)
167
+ inheritable_class_attr_writer(*names)
168
+ end
169
+
170
+
171
+ private
172
+
173
+ # Non-inheritable attrs won't get any starting value, even if it has been set
174
+ # on a superclass so we need to save the value and set them when the class is
175
+ # inherited.
176
+ #
177
+ # @param key [Symbol]
178
+ # @param value [Object]
179
+ #
180
+ def register_default(key, value)
181
+ registered_defaults[key] = value
182
+ end
183
+
184
+ def registered_defaults
185
+ @registered_defaults ||= {}
186
+ end
187
+
188
+ def inherited_with_attrs(klass)
189
+ inherited_without_attrs(klass) if respond_to?(:inherited_without_attrs)
190
+ if registered_defaults
191
+ new_attrs = registered_defaults.inject({}) do |a, (k, v)|
192
+ a.update(k => (v.dup rescue v))
193
+ end
194
+ else
195
+ new_attrs = {}
196
+ end
197
+
198
+ new_attrs.each do |k, v|
199
+ klass.instance_variable_set("@#{k}", v)
200
+ end
201
+ klass.instance_variable_set("@registered_defaults", new_attrs)
202
+ end
203
+ alias inherited_without_attrs inherited
204
+ alias inherited inherited_with_attrs
205
+
206
+ # Need to use this instead of AttrPlus.separate_arguments so that any defaults
207
+ # are properly set, but that is only with Class as this is to allow subclasses
208
+ # to inherit the default values, Modules can't be inherited.
209
+ #
210
+ # BUT they can be included so I will have to look into this!
211
+ def separate_arguments_and_set_defaults(args)
212
+ args, default = AttrPlus.separate_arguments(args)
213
+ if default
214
+ args.each do |arg|
215
+ register_default(arg, default)
216
+ end
217
+ end
218
+
219
+ [args, default]
220
+ end
221
+
222
+ end
@@ -0,0 +1,20 @@
1
+ # Place extra methods in here to avoid polluting Class and Module.
2
+ #
3
+ module AttrPlus
4
+
5
+ # @param args [Array]
6
+ # @return [Array[Array, Object]]
7
+ # An array where the first item is a list of arguments that were passed
8
+ # and the second (last) item is a default value that was given or nil.
9
+ #
10
+ def self.separate_arguments(args)
11
+ if args.size == 1 && args.first.is_a?(Hash)
12
+ [[args.first.keys[0]], args.first.values[0]]
13
+ elsif args.last.is_a?(Hash)
14
+ [args[0..-2], args.last[:default]]
15
+ else
16
+ [args, nil]
17
+ end
18
+ end
19
+
20
+ end
File without changes
@@ -0,0 +1,33 @@
1
+ require 'attr_plus/ext'
2
+
3
+ class Module
4
+
5
+ def module_attr_reader(*args)
6
+ names, default = AttrPlus.separate_arguments(args)
7
+ names.each do |name|
8
+ module_eval <<-EOS
9
+ def self.#{name}
10
+ @#{name}
11
+ end
12
+ EOS
13
+ self.instance_variable_set("@#{name}", (default.dup rescue default))
14
+ end
15
+ end
16
+
17
+ def module_attr_writer(*args)
18
+ names, default = AttrPlus.separate_arguments(args)
19
+ names.each do |name|
20
+ module_eval <<-EOS
21
+ def self.#{name}=(obj)
22
+ @#{name} = obj
23
+ end
24
+ EOS
25
+ end
26
+ end
27
+
28
+ def module_attr_accessor(*args)
29
+ module_attr_reader(*args)
30
+ module_attr_writer(*args)
31
+ end
32
+
33
+ end
@@ -0,0 +1,3 @@
1
+ require 'attr_plus/class'
2
+
3
+ puts "'class_attr' is being deprecated in favour of 'attr_plus/class', please update your code!"
@@ -0,0 +1,165 @@
1
+ require 'spec_helper'
2
+
3
+ describe Class do
4
+
5
+ describe "#class_attr_reader" do
6
+ subject { Class.new { class_attr_reader :test } }
7
+
8
+ it "defines a read method for the class" do
9
+ subject.should respond_to :test
10
+ end
11
+
12
+ it "defines a read method for instances of the class" do
13
+ subject.new.should respond_to :test
14
+ end
15
+ end
16
+
17
+ describe "#class_attr_writer" do
18
+ subject { Class.new { class_attr_writer :test } }
19
+
20
+ it "defines a write method for the class" do
21
+ subject.should respond_to :test=
22
+ end
23
+ end
24
+
25
+ describe "#class_attr_accessor" do
26
+ subject { Class.new { class_attr_accessor :test } }
27
+
28
+ it "defines a read method for the class" do
29
+ subject.should respond_to :test
30
+ end
31
+
32
+ it "defines a write method for the class" do
33
+ subject.should respond_to :test=
34
+ end
35
+
36
+ it "defines a read method for instances of the class" do
37
+ subject.new.should respond_to :test
38
+ end
39
+ end
40
+
41
+ # The subclass thing was starting to annoy me...
42
+ context "When there is a subclass of a class with class_attrs" do
43
+
44
+ let(:sup) { Class.new { class_attr_accessor :test } }
45
+ let(:sub) { Class.new(sup) }
46
+
47
+ it "has the methods of the superclass" do
48
+ sub.should respond_to :test
49
+ sub.should respond_to :test=
50
+ end
51
+
52
+ describe "calling the writer method on the subclass" do
53
+ it "should not alter the value on the superclass" do
54
+ sub.test = "changed"
55
+ sub.test.should == "changed"
56
+ sup.test.should_not == "changed"
57
+ end
58
+ end
59
+
60
+ describe "calling the writer method on the superclass" do
61
+ it "should not alter the value on the subclass" do
62
+ sup.test = "changed again"
63
+ sup.test.should == "changed again"
64
+ sub.test.should_not == "changed again"
65
+ end
66
+ end
67
+ end
68
+
69
+
70
+ # Inheritable class attributes
71
+
72
+ describe "#inheritable_class_attr_reader" do
73
+ subject { Class.new { inheritable_class_attr_reader :test } }
74
+
75
+ it "defines a read method for the class" do
76
+ subject.should respond_to :test
77
+ end
78
+
79
+ it "defines a read method for instances of the class" do
80
+ subject.new.should respond_to :test
81
+ end
82
+ end
83
+
84
+ describe "#inheritable_class_attr_writer" do
85
+ subject { Class.new { inheritable_class_attr_writer :test } }
86
+
87
+ it "defines a write method for the class" do
88
+ subject.should respond_to :test=
89
+ end
90
+ end
91
+
92
+ describe "#inheritable_class_attr_accessor" do
93
+ subject { Class.new { inheritable_class_attr_accessor :test } }
94
+
95
+ it "defines a read method for the class" do
96
+ subject.should respond_to :test
97
+ end
98
+
99
+ it "defines a write method for the class" do
100
+ subject.should respond_to :test=
101
+ end
102
+
103
+ it "defines a read method for instances of the class" do
104
+ subject.new.should respond_to :test
105
+ end
106
+ end
107
+
108
+ context "When there is a subclass of a class with inheritable_class_attrs" do
109
+
110
+ let(:sup) { Class.new { inheritable_class_attr_accessor :test } }
111
+ let(:sub) { Class.new(sup) }
112
+
113
+ it "has the methods of the superclass" do
114
+ sub.should respond_to :test
115
+ sub.should respond_to :test=
116
+ end
117
+
118
+ describe "calling the writer method on the subclass" do
119
+ it "should not alter the value on the superclass" do
120
+ sub.test = "changed"
121
+ sub.test.should == "changed"
122
+ sup.test.should_not == "changed"
123
+ end
124
+ end
125
+
126
+ describe "calling the writer method on the superclass" do
127
+ it "should alter the value on the subclass if not set" do
128
+ sup.test = "changed it"
129
+ sup.test.should == "changed it"
130
+ sub.test.should == "changed it"
131
+ end
132
+
133
+ it "should not alter the value on the subclass if set" do
134
+ sub.test = "already set"
135
+ sup.test = "change it"
136
+ sup.test.should == "change it"
137
+ sub.test.should == "already set"
138
+ end
139
+ end
140
+ end
141
+
142
+ context "When given a default value" do
143
+
144
+ subject { Class.new { class_attr_accessor :test => "hi" } }
145
+
146
+ it "returns the default value before it is changed" do
147
+ subject.test.should == "hi"
148
+ end
149
+
150
+ it "returns the new value if it is changed" do
151
+ subject.test = nil
152
+ subject.test.should be_nil
153
+ end
154
+
155
+ context "When this class is inherited" do
156
+ let(:sub) { Class.new(subject) }
157
+
158
+ it "returns the default value as well" do
159
+ sub.test.should == "hi"
160
+ end
161
+ end
162
+
163
+ end
164
+
165
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe Module do
4
+
5
+ describe "#module_attr_reader" do
6
+ subject { Module.new { module_attr_reader :test } }
7
+
8
+ it "defines a read method for the module" do
9
+ subject.should respond_to :test
10
+ end
11
+ end
12
+
13
+ describe "#module_attr_writer" do
14
+ subject { Module.new { module_attr_writer :test } }
15
+
16
+ it "defines a write method for the module" do
17
+ subject.should respond_to :test=
18
+ end
19
+ end
20
+
21
+ describe "#module_attr_accessor" do
22
+ subject { Module.new { module_attr_accessor :test } }
23
+
24
+ it "defines a read method for the module" do
25
+ subject.should respond_to :test
26
+ end
27
+
28
+ it "defines a write method for the module" do
29
+ subject.should respond_to :test=
30
+ end
31
+ end
32
+
33
+ context "When given a default value" do
34
+ subject { Module.new { module_attr_accessor :test => "hi" } }
35
+
36
+ it "returns the default value before being changed" do
37
+ subject.test.should == "hi"
38
+ end
39
+
40
+ it "returns the new value if it is changed" do
41
+ subject.test = nil
42
+ subject.test.should be_nil
43
+ end
44
+
45
+ context "when this module is included" do
46
+ subject { Class.new { include Module.new { module_attr_accessor :test => "hi" } } }
47
+
48
+ it "returns the default value as well" do
49
+ subject.test.should == "hi"
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe AttrPlus do
4
+
5
+ describe "#separate_argument_list_and_default" do
6
+ def call_it(*args)
7
+ AttrPlus.separate_arguments(*args)
8
+ end
9
+
10
+ context "when given a list of arguments with a default" do
11
+ it "returns an array with the arguments and a default value" do
12
+ call_it([:one, :two, :three, {:default => 0}]).should == [[:one, :two, :three], 0]
13
+ end
14
+ end
15
+
16
+ context "when given an argument and a default" do
17
+ it "returns an array with the arguments and a default value" do
18
+ call_it([{:one => 1}]).should == [[:one], 1]
19
+ end
20
+ end
21
+
22
+ context "when given just arguments" do
23
+ it "returns and array with the arguments and nil" do
24
+ call_it([:one, :two, :three]).should == [[:one, :two, :three], nil]
25
+ end
26
+ end
27
+ end
28
+
29
+ end
@@ -0,0 +1,11 @@
1
+ if RUBY_VERSION >= "1.9"
2
+ require 'duvet'
3
+ Duvet.start :filter => 'class_attr/lib'
4
+ end
5
+
6
+ require 'rspec'
7
+ require 'attr_plus'
8
+
9
+ RSpec.configure do |config|
10
+ config.color_enabled = true
11
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: attr_plus
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 2
8
+ - 0
9
+ version: 0.2.0
10
+ platform: ruby
11
+ authors:
12
+ - Joshua Hawxwell
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-02-12 00:00:00 +00:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: " Provides attr_accessor style methods for easily creating methods for\n working with class level instance variables and module level instance\n variables. Variables can be inherited and have default values.\n"
22
+ email: m@hawx.me
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files: []
28
+
29
+ files:
30
+ - README.md
31
+ - LICENSE
32
+ - lib/attr_plus/class.rb
33
+ - lib/attr_plus/ext.rb
34
+ - lib/attr_plus/instance.rb
35
+ - lib/attr_plus/module.rb
36
+ - lib/attr_plus.rb
37
+ - lib/class_attr.rb
38
+ - spec/attr_plus/class_spec.rb
39
+ - spec/attr_plus/module_spec.rb
40
+ - spec/class_attr_spec.rb
41
+ - spec/spec_helper.rb
42
+ has_rdoc: false
43
+ homepage: http://github.com/hawx/attr_plus
44
+ licenses: []
45
+
46
+ post_install_message:
47
+ rdoc_options: []
48
+
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ segments:
57
+ - 0
58
+ version: "0"
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ segments:
65
+ - 0
66
+ version: "0"
67
+ requirements: []
68
+
69
+ rubyforge_project:
70
+ rubygems_version: 1.3.7
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: attr_accessor for class and module level instance variables.
74
+ test_files:
75
+ - spec/attr_plus/class_spec.rb
76
+ - spec/attr_plus/module_spec.rb
77
+ - spec/class_attr_spec.rb
78
+ - spec/spec_helper.rb