scoped_struct 0.1

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