dsl_accessor 0.1.0 → 0.3.3
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 +20 -0
- data/README +175 -0
- data/Rakefile +70 -45
- data/core_ext/class/dsl_accessor.rb +93 -0
- data/core_ext/class/inheritable_attributes.rb +140 -0
- data/core_ext/duplicable.rb +43 -0
- data/core_ext/module/delegation.rb +95 -0
- data/lib/dsl_accessor.rb +8 -49
- data/spec/auto_declared_spec.rb +106 -0
- data/spec/inherit_spec.rb +21 -0
- data/spec/spec_helper.rb +5 -0
- data/tasks/dsl_accessor_tasks.rake +4 -0
- data/test/default_test.rb +64 -0
- data/test/dsl_accessor_test.rb +8 -81
- data/test/instance_options_test.rb +38 -0
- data/test/instance_test.rb +43 -0
- data/test/test_helper.rb +4 -2
- data/test/writer_test.rb +21 -0
- metadata +59 -58
- data/History.txt +0 -0
- data/Manifest.txt +0 -9
- data/README.txt +0 -97
- data/lib/dsl_accessor/version.rb +0 -9
- data/setup.rb +0 -1585
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 [maiha@wota.jp]
|
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, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
DslAccessor
|
2
|
+
===========
|
3
|
+
|
4
|
+
This plugin gives hybrid accessor class methods to classes by DSL like definition,
|
5
|
+
here hybrid means getter and setter. The accessor method acts as getter method
|
6
|
+
if no argments given, otherwise it acts as setter one with the arguments.
|
7
|
+
|
8
|
+
|
9
|
+
Usage
|
10
|
+
=====
|
11
|
+
|
12
|
+
class Foo
|
13
|
+
dsl_accessor "<METHOD NAME>"
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
Example
|
18
|
+
=======
|
19
|
+
|
20
|
+
class Foo
|
21
|
+
dsl_accessor :greeting
|
22
|
+
end
|
23
|
+
|
24
|
+
This code gives 'greeting' class method to Foo class.
|
25
|
+
|
26
|
+
Foo.greeting # means getter, and the default value is nil.
|
27
|
+
=> nil
|
28
|
+
|
29
|
+
Foo.greeting "I'm Foo." # means setter with given arguments
|
30
|
+
=> "I'm Foo."
|
31
|
+
|
32
|
+
Foo.greeting
|
33
|
+
=> "I'm Foo."
|
34
|
+
|
35
|
+
|
36
|
+
Difference
|
37
|
+
==========
|
38
|
+
|
39
|
+
I'm convinced that you want to propose me to use 'cattr_accessor'.
|
40
|
+
Although the difference is just whether we needs '=' operation or not,
|
41
|
+
it makes a large different on class definition especially subclass.
|
42
|
+
|
43
|
+
class Foo
|
44
|
+
cattr_accessor :greeting
|
45
|
+
end
|
46
|
+
|
47
|
+
class Bar < Foo
|
48
|
+
self.greeting = "I'm bar."
|
49
|
+
end
|
50
|
+
|
51
|
+
We must write redundant code represented by "self." to distinguish
|
52
|
+
a local variable and a class method when we use 'cattr_accessor'.
|
53
|
+
This is ugly and boring work.
|
54
|
+
|
55
|
+
class Foo
|
56
|
+
dsl_accessor :greeting
|
57
|
+
end
|
58
|
+
|
59
|
+
class Bar < Foo
|
60
|
+
greeting "I'm bar."
|
61
|
+
end
|
62
|
+
|
63
|
+
There are no longer redundant prefix code like "self." and "set_".
|
64
|
+
Don't you like this dsl-like coding with simple declaration?
|
65
|
+
|
66
|
+
|
67
|
+
Special Options
|
68
|
+
===============
|
69
|
+
|
70
|
+
'dsl_accessor' method can take two options, those are :writer and :default.
|
71
|
+
"writer" option means callback method used when setter is executed.
|
72
|
+
"default" option means default static value or proc that creates some value.
|
73
|
+
|
74
|
+
class PseudoAR
|
75
|
+
dsl_accessor :primary_key, :default=>"id", :writer=>proc{|value| value.to_s}
|
76
|
+
dsl_accessor :table_name, :default=>proc{|klass| klass.name.demodulize.underscore.pluralize}
|
77
|
+
end
|
78
|
+
|
79
|
+
class Item < PseudoAR
|
80
|
+
end
|
81
|
+
|
82
|
+
class User < PseudoAR
|
83
|
+
primary_key :user_code
|
84
|
+
table_name :user_table
|
85
|
+
end
|
86
|
+
|
87
|
+
Item.primary_key # => "id"
|
88
|
+
Item.table_name # => "items"
|
89
|
+
User.primary_key # => "user_code"
|
90
|
+
User.table_name # => :user_table
|
91
|
+
|
92
|
+
Note that "User.primary_key" return a String by setter proc.
|
93
|
+
|
94
|
+
|
95
|
+
Instance Method
|
96
|
+
===============
|
97
|
+
|
98
|
+
"instance" option automatically defines its instance method
|
99
|
+
|
100
|
+
class Search
|
101
|
+
dsl_accessor :url, :instance=>true, :default=>"http://localhost/"
|
102
|
+
end
|
103
|
+
|
104
|
+
Search.url # => "http://localhost/"
|
105
|
+
Search.new.url # => "http://localhost/"
|
106
|
+
|
107
|
+
and it uses @options instance variable with special value :options
|
108
|
+
|
109
|
+
class Window
|
110
|
+
dsl_accessor :width, :default=>640, :instance=>:options
|
111
|
+
def initialize(options = {})
|
112
|
+
@options = options
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
Window.width # => 640
|
117
|
+
Window.new.width # => 640
|
118
|
+
|
119
|
+
window = Window.new(:width=>320)
|
120
|
+
window.width # =>320
|
121
|
+
|
122
|
+
|
123
|
+
Install
|
124
|
+
=======
|
125
|
+
|
126
|
+
git://github.com/maiha/dsl_accessor.git
|
127
|
+
|
128
|
+
|
129
|
+
Auto declared mode
|
130
|
+
==================
|
131
|
+
|
132
|
+
Calling dsl_accessor without args enters auto declared mode.
|
133
|
+
In this mode, a method missing means instance method creation.
|
134
|
+
This affects only methods with a block and no other args.
|
135
|
+
|
136
|
+
class Foo
|
137
|
+
dsl_accessor # auto declared mode
|
138
|
+
foo{1} # define :foo
|
139
|
+
bar(a) # NoMethodError
|
140
|
+
baz(a){2} # NoMethodError
|
141
|
+
end
|
142
|
+
|
143
|
+
Foo.new.foo # => 1
|
144
|
+
|
145
|
+
This is useful when you have many methods those are one lined methods.
|
146
|
+
|
147
|
+
[without auto delared mode]
|
148
|
+
class Foo
|
149
|
+
def last
|
150
|
+
num_pages
|
151
|
+
end
|
152
|
+
|
153
|
+
def first?
|
154
|
+
page == 1
|
155
|
+
end
|
156
|
+
|
157
|
+
def offset
|
158
|
+
model.proxy_options[:offset]
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
[with auto delared mode]
|
163
|
+
class Foo
|
164
|
+
dsl_accessor
|
165
|
+
last {num_pages}
|
166
|
+
first? {page == 1}
|
167
|
+
offset {model.proxy_options[:offset]}
|
168
|
+
end
|
169
|
+
|
170
|
+
|
171
|
+
|
172
|
+
Author
|
173
|
+
======
|
174
|
+
Maiha <maiha@wota.jp>
|
175
|
+
|
data/Rakefile
CHANGED
@@ -1,49 +1,74 @@
|
|
1
|
-
require 'rubygems'
|
2
1
|
require 'rake'
|
3
|
-
require 'rake/clean'
|
4
2
|
require 'rake/testtask'
|
5
|
-
require 'rake/packagetask'
|
6
|
-
require 'rake/gempackagetask'
|
7
3
|
require 'rake/rdoctask'
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the dsl_accessor plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.pattern = 'test/**/*_test.rb'
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Generate documentation for the dsl_accessor plugin.'
|
16
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'DslAccessor'
|
19
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
20
|
+
rdoc.rdoc_files.include('README')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
######################################################################
|
26
|
+
### for gem
|
27
|
+
|
28
|
+
require 'rubygems'
|
29
|
+
require 'rake/gempackagetask'
|
30
|
+
|
17
31
|
GEM_NAME = "dsl_accessor"
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
32
|
+
AUTHOR = "maiha"
|
33
|
+
EMAIL = "maiha@wota.jp"
|
34
|
+
HOMEPAGE = "http://github.com/maiha/dsl_accessor"
|
35
|
+
SUMMARY = "This plugin gives hybrid accessor class methods to classes by DSL like definition"
|
36
|
+
GEM_VERSION = "0.3.3"
|
37
|
+
|
38
|
+
spec = Gem::Specification.new do |s|
|
39
|
+
# s.rubyforge_project = 'merb'
|
40
|
+
s.name = GEM_NAME
|
41
|
+
s.version = GEM_VERSION
|
42
|
+
s.platform = Gem::Platform::RUBY
|
43
|
+
s.has_rdoc = true
|
44
|
+
s.extra_rdoc_files = ["README", "LICENSE"]
|
45
|
+
s.summary = SUMMARY
|
46
|
+
s.description = s.summary
|
47
|
+
s.author = AUTHOR
|
48
|
+
s.email = EMAIL
|
49
|
+
s.homepage = HOMEPAGE
|
50
|
+
s.require_path = 'lib'
|
51
|
+
s.files = %w(LICENSE README Rakefile) + Dir.glob("{core_ext,lib,spec,tasks,test}/**/*")
|
52
|
+
end
|
53
|
+
|
54
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
55
|
+
pkg.gem_spec = spec
|
56
|
+
end
|
57
|
+
|
58
|
+
desc "Install the gem"
|
59
|
+
task :install do
|
60
|
+
Merb::RakeHelper.install(GEM_NAME, :version => GEM_VERSION)
|
61
|
+
end
|
62
|
+
|
63
|
+
desc "Uninstall the gem"
|
64
|
+
task :uninstall do
|
65
|
+
Merb::RakeHelper.uninstall(GEM_NAME, :version => GEM_VERSION)
|
66
|
+
end
|
67
|
+
|
68
|
+
desc "Create a gemspec file"
|
69
|
+
task :gemspec do
|
70
|
+
File.open("#{GEM_NAME}.gemspec", "w") do |file|
|
71
|
+
file.puts spec.to_ruby
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module DslAccessor
|
2
|
+
def dsl_accessor(*args)
|
3
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
4
|
+
|
5
|
+
# mark auto_declared
|
6
|
+
name = args.shift or
|
7
|
+
return @dsl_accessor_auto_declared = true
|
8
|
+
|
9
|
+
options[:default] = args.shift unless args.empty?
|
10
|
+
|
11
|
+
case options[:instance]
|
12
|
+
when nil
|
13
|
+
# nop
|
14
|
+
when :options
|
15
|
+
module_eval(<<-EOS, "(__DSL_ACCESSOR__)", 1)
|
16
|
+
def #{ name }
|
17
|
+
unless @options
|
18
|
+
raise TypeError, "DSL Error: missing @options for %s##{name}" % self.class.name
|
19
|
+
end
|
20
|
+
@options[:#{ name }] || self.class.#{ name }
|
21
|
+
end
|
22
|
+
EOS
|
23
|
+
when true
|
24
|
+
delegate name, :to=>"self.class"
|
25
|
+
else
|
26
|
+
raise TypeError, "DSL Error: :instance should be true or :instance, but got `%s' class" % options[:instance].class
|
27
|
+
end
|
28
|
+
|
29
|
+
raise TypeError, "DSL Error: options should be a hash. but got `#{options.class}'" unless options.is_a?(Hash)
|
30
|
+
writer = options[:writer] || options[:setter]
|
31
|
+
writer =
|
32
|
+
case writer
|
33
|
+
when NilClass then Proc.new{|value| value}
|
34
|
+
when Symbol then Proc.new{|value| __send__(writer, value)}
|
35
|
+
when Proc then writer
|
36
|
+
else raise TypeError, "DSL Error: writer should be a symbol or proc. but got `#{options[:writer].class}'"
|
37
|
+
end
|
38
|
+
write_inheritable_attribute(:"#{name}_writer", writer)
|
39
|
+
|
40
|
+
default =
|
41
|
+
case options[:default]
|
42
|
+
when NilClass then nil
|
43
|
+
when [] then Proc.new{[]}
|
44
|
+
when {} then Proc.new{{}}
|
45
|
+
when Symbol then Proc.new{__send__(options[:default])}
|
46
|
+
when Proc then options[:default]
|
47
|
+
else Proc.new{options[:default]}
|
48
|
+
end
|
49
|
+
write_inheritable_attribute(:"#{name}_default", default)
|
50
|
+
|
51
|
+
(class << self; self end).class_eval do
|
52
|
+
define_method("#{name}=") do |value|
|
53
|
+
writer = read_inheritable_attribute(:"#{name}_writer")
|
54
|
+
value = writer.call(value) if writer
|
55
|
+
write_inheritable_attribute(:"#{name}", value)
|
56
|
+
end
|
57
|
+
|
58
|
+
define_method(name) do |*values|
|
59
|
+
if values.empty?
|
60
|
+
# getter method
|
61
|
+
key = :"#{name}"
|
62
|
+
if !inheritable_attributes.has_key?(key)
|
63
|
+
default = read_inheritable_attribute(:"#{name}_default")
|
64
|
+
value = default ? default.call(self) : nil
|
65
|
+
__send__("#{name}=", value)
|
66
|
+
end
|
67
|
+
read_inheritable_attribute(key)
|
68
|
+
else
|
69
|
+
# setter method
|
70
|
+
__send__("#{name}=", *values)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
def dsl_accessor_auto_declared?
|
78
|
+
!!@dsl_accessor_auto_declared
|
79
|
+
end
|
80
|
+
|
81
|
+
def method_missing(*args, &block)
|
82
|
+
if dsl_accessor_auto_declared? and args.size == 1 and block
|
83
|
+
define_method(*args, &block)
|
84
|
+
else
|
85
|
+
super
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
class Class
|
92
|
+
include DslAccessor
|
93
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# Retain for backward compatibility. Methods are now included in Class.
|
2
|
+
module ClassInheritableAttributes # :nodoc:
|
3
|
+
end
|
4
|
+
|
5
|
+
# Allows attributes to be shared within an inheritance hierarchy, but where each descendant gets a copy of
|
6
|
+
# their parents' attributes, instead of just a pointer to the same. This means that the child can add elements
|
7
|
+
# to, for example, an array without those additions being shared with either their parent, siblings, or
|
8
|
+
# children, which is unlike the regular class-level attributes that are shared across the entire hierarchy.
|
9
|
+
class Class # :nodoc:
|
10
|
+
def class_inheritable_reader(*syms)
|
11
|
+
syms.each do |sym|
|
12
|
+
next if sym.is_a?(Hash)
|
13
|
+
class_eval <<-EOS
|
14
|
+
def self.#{sym}
|
15
|
+
read_inheritable_attribute(:#{sym})
|
16
|
+
end
|
17
|
+
|
18
|
+
def #{sym}
|
19
|
+
self.class.#{sym}
|
20
|
+
end
|
21
|
+
EOS
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def class_inheritable_writer(*syms)
|
26
|
+
options = syms.last.is_a?(::Hash) ? syms.pop : {}
|
27
|
+
syms.each do |sym|
|
28
|
+
class_eval <<-EOS
|
29
|
+
def self.#{sym}=(obj)
|
30
|
+
write_inheritable_attribute(:#{sym}, obj)
|
31
|
+
end
|
32
|
+
|
33
|
+
#{"
|
34
|
+
def #{sym}=(obj)
|
35
|
+
self.class.#{sym} = obj
|
36
|
+
end
|
37
|
+
" unless options[:instance_writer] == false }
|
38
|
+
EOS
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def class_inheritable_array_writer(*syms)
|
43
|
+
options = syms.last.is_a?(::Hash) ? syms.pop : {}
|
44
|
+
syms.each do |sym|
|
45
|
+
class_eval <<-EOS
|
46
|
+
def self.#{sym}=(obj)
|
47
|
+
write_inheritable_array(:#{sym}, obj)
|
48
|
+
end
|
49
|
+
|
50
|
+
#{"
|
51
|
+
def #{sym}=(obj)
|
52
|
+
self.class.#{sym} = obj
|
53
|
+
end
|
54
|
+
" unless options[:instance_writer] == false }
|
55
|
+
EOS
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def class_inheritable_hash_writer(*syms)
|
60
|
+
options = syms.last.is_a?(::Hash) ? syms.pop : {}
|
61
|
+
syms.each do |sym|
|
62
|
+
class_eval <<-EOS
|
63
|
+
def self.#{sym}=(obj)
|
64
|
+
write_inheritable_hash(:#{sym}, obj)
|
65
|
+
end
|
66
|
+
|
67
|
+
#{"
|
68
|
+
def #{sym}=(obj)
|
69
|
+
self.class.#{sym} = obj
|
70
|
+
end
|
71
|
+
" unless options[:instance_writer] == false }
|
72
|
+
EOS
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def class_inheritable_accessor(*syms)
|
77
|
+
class_inheritable_reader(*syms)
|
78
|
+
class_inheritable_writer(*syms)
|
79
|
+
end
|
80
|
+
|
81
|
+
def class_inheritable_array(*syms)
|
82
|
+
class_inheritable_reader(*syms)
|
83
|
+
class_inheritable_array_writer(*syms)
|
84
|
+
end
|
85
|
+
|
86
|
+
def class_inheritable_hash(*syms)
|
87
|
+
class_inheritable_reader(*syms)
|
88
|
+
class_inheritable_hash_writer(*syms)
|
89
|
+
end
|
90
|
+
|
91
|
+
def inheritable_attributes
|
92
|
+
@inheritable_attributes ||= EMPTY_INHERITABLE_ATTRIBUTES
|
93
|
+
end
|
94
|
+
|
95
|
+
def write_inheritable_attribute(key, value)
|
96
|
+
if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
|
97
|
+
@inheritable_attributes = {}
|
98
|
+
end
|
99
|
+
inheritable_attributes[key] = value
|
100
|
+
end
|
101
|
+
|
102
|
+
def write_inheritable_array(key, elements)
|
103
|
+
write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil?
|
104
|
+
write_inheritable_attribute(key, read_inheritable_attribute(key) + elements)
|
105
|
+
end
|
106
|
+
|
107
|
+
def write_inheritable_hash(key, hash)
|
108
|
+
write_inheritable_attribute(key, {}) if read_inheritable_attribute(key).nil?
|
109
|
+
write_inheritable_attribute(key, read_inheritable_attribute(key).merge(hash))
|
110
|
+
end
|
111
|
+
|
112
|
+
def read_inheritable_attribute(key)
|
113
|
+
inheritable_attributes[key]
|
114
|
+
end
|
115
|
+
|
116
|
+
def reset_inheritable_attributes
|
117
|
+
@inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
# Prevent this constant from being created multiple times
|
122
|
+
EMPTY_INHERITABLE_ATTRIBUTES = {}.freeze unless const_defined?(:EMPTY_INHERITABLE_ATTRIBUTES)
|
123
|
+
|
124
|
+
def inherited_with_inheritable_attributes(child)
|
125
|
+
inherited_without_inheritable_attributes(child) if respond_to?(:inherited_without_inheritable_attributes)
|
126
|
+
|
127
|
+
if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
|
128
|
+
new_inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
|
129
|
+
else
|
130
|
+
new_inheritable_attributes = inheritable_attributes.inject({}) do |memo, (key, value)|
|
131
|
+
memo.update(key => value.duplicable? ? value.dup : value)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
child.instance_variable_set('@inheritable_attributes', new_inheritable_attributes)
|
136
|
+
end
|
137
|
+
|
138
|
+
alias inherited_without_inheritable_attributes inherited
|
139
|
+
alias inherited inherited_with_inheritable_attributes
|
140
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class Object
|
2
|
+
# Can you safely .dup this object?
|
3
|
+
# False for nil, false, true, symbols, and numbers; true otherwise.
|
4
|
+
def duplicable?
|
5
|
+
true
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class NilClass #:nodoc:
|
10
|
+
def duplicable?
|
11
|
+
false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class FalseClass #:nodoc:
|
16
|
+
def duplicable?
|
17
|
+
false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class TrueClass #:nodoc:
|
22
|
+
def duplicable?
|
23
|
+
false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Symbol #:nodoc:
|
28
|
+
def duplicable?
|
29
|
+
false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Numeric #:nodoc:
|
34
|
+
def duplicable?
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Class #:nodoc:
|
40
|
+
def duplicable?
|
41
|
+
false
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
class Module
|
2
|
+
# Provides a delegate class method to easily expose contained objects' methods
|
3
|
+
# as your own. Pass one or more methods (specified as symbols or strings)
|
4
|
+
# and the name of the target object as the final <tt>:to</tt> option (also a symbol
|
5
|
+
# or string). At least one method and the <tt>:to</tt> option are required.
|
6
|
+
#
|
7
|
+
# Delegation is particularly useful with Active Record associations:
|
8
|
+
#
|
9
|
+
# class Greeter < ActiveRecord::Base
|
10
|
+
# def hello() "hello" end
|
11
|
+
# def goodbye() "goodbye" end
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# class Foo < ActiveRecord::Base
|
15
|
+
# belongs_to :greeter
|
16
|
+
# delegate :hello, :to => :greeter
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# Foo.new.hello # => "hello"
|
20
|
+
# Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
|
21
|
+
#
|
22
|
+
# Multiple delegates to the same target are allowed:
|
23
|
+
#
|
24
|
+
# class Foo < ActiveRecord::Base
|
25
|
+
# belongs_to :greeter
|
26
|
+
# delegate :hello, :goodbye, :to => :greeter
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# Foo.new.goodbye # => "goodbye"
|
30
|
+
#
|
31
|
+
# Methods can be delegated to instance variables, class variables, or constants
|
32
|
+
# by providing them as a symbols:
|
33
|
+
#
|
34
|
+
# class Foo
|
35
|
+
# CONSTANT_ARRAY = [0,1,2,3]
|
36
|
+
# @@class_array = [4,5,6,7]
|
37
|
+
#
|
38
|
+
# def initialize
|
39
|
+
# @instance_array = [8,9,10,11]
|
40
|
+
# end
|
41
|
+
# delegate :sum, :to => :CONSTANT_ARRAY
|
42
|
+
# delegate :min, :to => :@@class_array
|
43
|
+
# delegate :max, :to => :@instance_array
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# Foo.new.sum # => 6
|
47
|
+
# Foo.new.min # => 4
|
48
|
+
# Foo.new.max # => 11
|
49
|
+
#
|
50
|
+
# Delegates can optionally be prefixed using the <tt>:prefix</tt> option. If the value
|
51
|
+
# is <tt>true</tt>, the delegate methods are prefixed with the name of the object being
|
52
|
+
# delegated to.
|
53
|
+
#
|
54
|
+
# Person = Struct.new(:name, :address)
|
55
|
+
#
|
56
|
+
# class Invoice < Struct.new(:client)
|
57
|
+
# delegate :name, :address, :to => :client, :prefix => true
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# john_doe = Person.new("John Doe", "Vimmersvej 13")
|
61
|
+
# invoice = Invoice.new(john_doe)
|
62
|
+
# invoice.client_name # => "John Doe"
|
63
|
+
# invoice.client_address # => "Vimmersvej 13"
|
64
|
+
#
|
65
|
+
# It is also possible to supply a custom prefix.
|
66
|
+
#
|
67
|
+
# class Invoice < Struct.new(:client)
|
68
|
+
# delegate :name, :address, :to => :client, :prefix => :customer
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# invoice = Invoice.new(john_doe)
|
72
|
+
# invoice.customer_name # => "John Doe"
|
73
|
+
# invoice.customer_address # => "Vimmersvej 13"
|
74
|
+
#
|
75
|
+
def delegate(*methods)
|
76
|
+
options = methods.pop
|
77
|
+
unless options.is_a?(Hash) && to = options[:to]
|
78
|
+
raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."
|
79
|
+
end
|
80
|
+
|
81
|
+
if options[:prefix] == true && options[:to].to_s =~ /^[^a-z_]/
|
82
|
+
raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
|
83
|
+
end
|
84
|
+
|
85
|
+
prefix = options[:prefix] && "#{options[:prefix] == true ? to : options[:prefix]}_"
|
86
|
+
|
87
|
+
methods.each do |method|
|
88
|
+
module_eval(<<-EOS, "(__DELEGATION__)", 1)
|
89
|
+
def #{prefix}#{method}(*args, &block)
|
90
|
+
#{to}.__send__(#{method.inspect}, *args, &block)
|
91
|
+
end
|
92
|
+
EOS
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|