attr_plus 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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