scoped_struct 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README +100 -0
- data/Rakefile +46 -0
- data/VERSION +1 -0
- data/lib/scoped_struct.rb +56 -0
- data/test/scoped_struct_test.rb +56 -0
- metadata +64 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Mike Ferrier
|
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,100 @@
|
|
1
|
+
== Scoped Struct
|
2
|
+
|
3
|
+
Ever write a ruby class and wish you could organize your methods into sub-objects? Take, for example, a class that represents an NFL player:
|
4
|
+
|
5
|
+
class Player
|
6
|
+
def fumbles_dropped; end
|
7
|
+
def fumbles_lost; end
|
8
|
+
def fumbles_recovered; end
|
9
|
+
def passes_attempted; end
|
10
|
+
def passes_completed; end
|
11
|
+
def passes_incomplete; end
|
12
|
+
def pass_completion_percentage; end
|
13
|
+
end
|
14
|
+
|
15
|
+
But we're ~Ruby Programmers~ and we write DRY code, right? Wouldn't it be nicer if you could take all that repetition and eliminate it?
|
16
|
+
|
17
|
+
class Player
|
18
|
+
scope :fumbles do
|
19
|
+
def dropped; end
|
20
|
+
def lost; end
|
21
|
+
def recovered; end
|
22
|
+
end
|
23
|
+
|
24
|
+
scope :passes do
|
25
|
+
def attempted; end
|
26
|
+
def completed; end
|
27
|
+
def incomplete; end
|
28
|
+
def completion_percentage; end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
And then instead of those long ugly method names, you could just be all like,
|
33
|
+
|
34
|
+
my_player.passes.attempted
|
35
|
+
my_player.passes.completed
|
36
|
+
my_player.fumbles.recovered
|
37
|
+
|
38
|
+
Eez better, yes?
|
39
|
+
|
40
|
+
The methods you define in the scoping block, when called, run in the same context as if you defined them on the class itself. So you ccan have them referencing instance variables or other methods of the class as if they were local. Likewise, you can also reference other scoped methods from scoped methods, even from other scopes; just make sure to reference them by their full scoped path:
|
41
|
+
|
42
|
+
class Player
|
43
|
+
def name
|
44
|
+
"John Rambo"
|
45
|
+
end
|
46
|
+
|
47
|
+
scope :formatting do
|
48
|
+
def loud_name
|
49
|
+
name.upcase + '!!!'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
scope :actions do
|
54
|
+
def scream_my_name
|
55
|
+
puts "MY NAME IS #{formatting.loud_name}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def scream_twice
|
59
|
+
2.times{ actions.scream_my_name }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
Player.new.actions.scream_twice # prints "MY NAME IS JOHN RAMBO!!!" twice
|
65
|
+
|
66
|
+
And that's not all! The object returned by the scoping method, you can pass it around and it will still act the way you'd expect:
|
67
|
+
|
68
|
+
fumble_records = my_players.collect{|player| player.fumbles}
|
69
|
+
fumble_records.first.dropped
|
70
|
+
|
71
|
+
And if you want even more organizational fun, you can stick your methods in modules and then include them in their own scoping blocks:
|
72
|
+
|
73
|
+
module Passes
|
74
|
+
def attempted; end
|
75
|
+
end
|
76
|
+
|
77
|
+
module Fumbles
|
78
|
+
def dropped; end
|
79
|
+
end
|
80
|
+
|
81
|
+
class Player
|
82
|
+
scope :passes do
|
83
|
+
include Passes
|
84
|
+
end
|
85
|
+
|
86
|
+
scope :fumbles do
|
87
|
+
include Fumbles
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
== Troubleshooting
|
93
|
+
|
94
|
+
One thing that will cause weirdness is if you name a scoped method the same thing as one of your base instance methods. Rename it to something else and all will be well.
|
95
|
+
|
96
|
+
== Credits
|
97
|
+
|
98
|
+
By Mike Ferrier (http://www.mikeferrier.ca)
|
99
|
+
|
100
|
+
Contributions by Hampton Catlin (http://www.hamptoncatlin.com)
|
data/Rakefile
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
Gem::manage_gems
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
|
5
|
+
spec = Gem::Specification.new do |s|
|
6
|
+
s.name = "scoped_struct"
|
7
|
+
s.summary = "A library that allows Ruby programmers to better organize their instance methods."
|
8
|
+
s.version = File.read('VERSION').strip
|
9
|
+
s.author = "Mike Ferrier"
|
10
|
+
s.email = "mferrier@gmail.com"
|
11
|
+
s.homepage = "http://scopedstruct.rubyforge.org"
|
12
|
+
s.platform = Gem::Platform::RUBY
|
13
|
+
s.summary = "http://scopedstruct.rubyforge.org"
|
14
|
+
|
15
|
+
readmes = FileList.new('*') do |list|
|
16
|
+
list.exclude(/[a-z]/)
|
17
|
+
list.exclude('TODO')
|
18
|
+
end.to_a
|
19
|
+
s.files = FileList['lib/**/*', 'test/**/*', 'Rakefile'].to_a + readmes
|
20
|
+
|
21
|
+
s.require_path = "lib"
|
22
|
+
s.rubyforge_project = "scopedstruct"
|
23
|
+
s.has_rdoc = true
|
24
|
+
s.extra_rdoc_files = ["README", "MIT-LICENSE"]
|
25
|
+
s.rdoc_options += [
|
26
|
+
'--title', 'Scoped Struct',
|
27
|
+
'--main', 'README',
|
28
|
+
'--line-numbers',
|
29
|
+
'--inline-source'
|
30
|
+
]
|
31
|
+
|
32
|
+
s.test_files = FileList['test/**/*_test.rb'].to_a
|
33
|
+
end
|
34
|
+
|
35
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
36
|
+
pkg.need_tar = true
|
37
|
+
pkg.need_zip = true
|
38
|
+
end
|
39
|
+
|
40
|
+
task :release => [:package] do
|
41
|
+
name, version = ENV['NAME'], ENV['VERSION']
|
42
|
+
raise "Must supply NAME and VERSION for release task." unless name && version
|
43
|
+
sh %{rubyforge login}
|
44
|
+
sh %{rubyforge add_release scopedstruct scopedstruct "#{name} (v#{version})" pkg/scoped_struct-#{version}.gem}
|
45
|
+
sh %{rubyforge add_file scopedstruct scopedstruct "#{name} (v#{version})" pkg/scoped_struct-#{version}.zip}
|
46
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module ScopedStruct
|
2
|
+
|
3
|
+
module ClassMethods
|
4
|
+
def scope(scope_name, &block)
|
5
|
+
MethodCarrier.set_scoped_methods(scope_name, block)
|
6
|
+
self.extend MethodCarrier
|
7
|
+
self.send(:define_method, scope_name) do
|
8
|
+
ProxyObject.new(self, scope_name)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class ProxyObject
|
14
|
+
def initialize(parent, scope_name)
|
15
|
+
@parent, @scope_name = parent, scope_name
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(name, *args, &block)
|
19
|
+
@parent.send(@scope_name.to_s + "_" + name.to_s, *args, &block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module MethodCarrier
|
24
|
+
def self.extend_object(base)
|
25
|
+
@@method_names.each do |method_name|
|
26
|
+
base.class_eval %Q(
|
27
|
+
alias #{@@scope_name + '_' + method_name} #{method_name}
|
28
|
+
undef #{method_name}
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.set_scoped_methods(scope_name, method_declarations)
|
34
|
+
raise SyntaxError.new("No block passed to scope command.") if method_declarations.nil?
|
35
|
+
@@scope_name = scope_name.to_s
|
36
|
+
@@method_names = extract_method_names(method_declarations).collect{|m| m.to_s}
|
37
|
+
raise SyntaxError.new("No methods defined in scope block.") unless @@method_names.any?
|
38
|
+
method_declarations.call
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.extract_method_names(method_declarations)
|
42
|
+
cls = BlankSlate.new
|
43
|
+
original_methods = cls.methods
|
44
|
+
cls.extend(Module.new(&method_declarations))
|
45
|
+
cls.methods - original_methods
|
46
|
+
end
|
47
|
+
|
48
|
+
# Jim Weirich's BlankSlate class from http://onestepback.org/index.cgi/Tech/Ruby/BlankSlate.rdoc
|
49
|
+
# We use a slightly modified version of it to figure out what methods were defined in the scope block
|
50
|
+
class BlankSlate
|
51
|
+
instance_methods.each { |m| undef_method m unless m =~ /^(__|methods|extend)/ }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
Object.extend(ScopedStruct::ClassMethods)
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + "/../lib/scoped_struct")
|
3
|
+
|
4
|
+
module Passing
|
5
|
+
def attempts
|
6
|
+
50
|
7
|
+
end
|
8
|
+
|
9
|
+
def completions
|
10
|
+
25
|
11
|
+
end
|
12
|
+
|
13
|
+
def percent_completed
|
14
|
+
passing.completions.to_f / passing.attempts.to_f
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module Defense
|
19
|
+
def tackles
|
20
|
+
23
|
21
|
+
end
|
22
|
+
|
23
|
+
def passes_blocked
|
24
|
+
8
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Player
|
29
|
+
|
30
|
+
def name
|
31
|
+
"jimmy"
|
32
|
+
end
|
33
|
+
|
34
|
+
scope :passing do
|
35
|
+
include Passing
|
36
|
+
end
|
37
|
+
|
38
|
+
scope :defense do
|
39
|
+
include Defense
|
40
|
+
end
|
41
|
+
|
42
|
+
scope :foo do
|
43
|
+
def scream_name
|
44
|
+
name.upcase
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class ScopedStructTest < Test::Unit::TestCase
|
50
|
+
def test_it
|
51
|
+
assert_equal 50, Player.new.passing.attempts
|
52
|
+
assert_equal 23, Player.new.defense.tackles
|
53
|
+
assert_equal 0.5, Player.new.passing.percent_completed
|
54
|
+
assert_equal 'JIMMY', Player.new.foo.scream_name
|
55
|
+
end
|
56
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: scoped_struct
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.1"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mike Ferrier
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-01-27 00:00:00 +00:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: mferrier@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
- MIT-LICENSE
|
25
|
+
files:
|
26
|
+
- lib/scoped_struct.rb
|
27
|
+
- test/scoped_struct_test.rb
|
28
|
+
- Rakefile
|
29
|
+
- VERSION
|
30
|
+
- MIT-LICENSE
|
31
|
+
- README
|
32
|
+
has_rdoc: true
|
33
|
+
homepage: http://scopedstruct.rubyforge.org
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options:
|
36
|
+
- --title
|
37
|
+
- Scoped Struct
|
38
|
+
- --main
|
39
|
+
- README
|
40
|
+
- --line-numbers
|
41
|
+
- --inline-source
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: "0"
|
49
|
+
version:
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
version:
|
56
|
+
requirements: []
|
57
|
+
|
58
|
+
rubyforge_project: scopedstruct
|
59
|
+
rubygems_version: 1.0.1
|
60
|
+
signing_key:
|
61
|
+
specification_version: 2
|
62
|
+
summary: http://scopedstruct.rubyforge.org
|
63
|
+
test_files:
|
64
|
+
- test/scoped_struct_test.rb
|