rulebook 0.3.3 → 0.4.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/.document +1 -1
- data/Gemfile +7 -0
- data/Gemfile.lock +21 -0
- data/LICENSE +1 -1
- data/README.md +41 -47
- data/Rakefile +33 -54
- data/VERSION +1 -1
- data/examples/simple.rb +43 -0
- data/lib/rulebook.rb +14 -153
- data/lib/rulebook/class_methods.rb +6 -0
- data/lib/rulebook/core_ext/module.rb +24 -0
- data/lib/rulebook/instance_methods.rb +41 -0
- data/lib/rulebook/rule.rb +18 -0
- data/rulebook.gemspec +49 -29
- data/test/helper.rb +10 -8
- data/test/test_chevy.rb +56 -69
- data/test/test_rule.rb +10 -0
- metadata +147 -54
- data/.gitignore +0 -21
- data/TODO +0 -1
- data/test/test_class_methods.rb +0 -53
- data/test/test_rules.rb +0 -90
- data/test/test_ryguy.rb +0 -45
- data/test/test_user.rb +0 -48
data/.document
CHANGED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
GEM
|
2
|
+
specs:
|
3
|
+
git (1.2.5)
|
4
|
+
jeweler (1.5.2)
|
5
|
+
bundler (~> 1.0.0)
|
6
|
+
git (>= 1.2.5)
|
7
|
+
rake
|
8
|
+
meta_tools (0.1.0)
|
9
|
+
rake (0.8.7)
|
10
|
+
riot (0.12.3)
|
11
|
+
rr
|
12
|
+
rr (1.0.2)
|
13
|
+
|
14
|
+
PLATFORMS
|
15
|
+
x86-mingw32
|
16
|
+
|
17
|
+
DEPENDENCIES
|
18
|
+
jeweler
|
19
|
+
meta_tools
|
20
|
+
rake
|
21
|
+
riot
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# rulebook
|
1
|
+
# rulebook 
|
2
2
|
|
3
3
|
Allows you to define a set of 'rules' or dynamic methods to apply to a class.
|
4
4
|
|
@@ -7,68 +7,56 @@ Allows you to define a set of 'rules' or dynamic methods to apply to a class.
|
|
7
7
|
> gem update --system
|
8
8
|
> gem install rulebook
|
9
9
|
|
10
|
-
## _Notice_
|
11
|
-
|
12
|
-
The format is _back_ to what it was before 0.2.2!
|
13
|
-
|
14
10
|
## Simple Example
|
15
11
|
|
16
12
|
require 'rulebook'
|
17
13
|
|
18
14
|
class User
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
15
|
+
follows_the_rules!
|
16
|
+
|
17
|
+
def initialize(name)
|
18
|
+
@name = name
|
19
|
+
end
|
20
|
+
|
21
|
+
rulebook.add /say_(.+)/ do |what_to_say|
|
22
|
+
puts "#{@name} says '#{what_to_say.gsub(/_/, ' ')}'"
|
23
|
+
end
|
26
24
|
end
|
27
25
|
|
28
26
|
User.new('Ryan').say_hello_world # => Ryan says 'hello world'
|
29
27
|
|
30
28
|
## How It Works
|
31
29
|
|
32
|
-
|
33
|
-
|
34
|
-
When the first time `rule` is called in a class, we also include the `RuleBook::IncludeMethods` module which overrides the classes `method_missing`. So when you call an undefined method on your class's instance, we will try to match the method against the rules you've defined in `INSTANCE_RULEBOOK`.
|
35
|
-
|
36
|
-
There is also a method called `class_rule` which does the same as rules does, only it defines the `CLASS_RULEBOOK` constance in the class; which is a different `RuleBook` instance. The first time `class_rules` is called, it extends the class with the `RuleBook::ExtendMethods` module, which also contains a `method_missing` method.
|
30
|
+
TODO
|
37
31
|
|
38
32
|
## Better Example
|
39
33
|
|
40
34
|
require 'rulebook'
|
41
35
|
|
42
36
|
class User
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
37
|
+
attr :name, :title
|
38
|
+
|
39
|
+
def initialize(name)
|
40
|
+
@name = name
|
41
|
+
@title = :user
|
42
|
+
end
|
43
|
+
|
44
|
+
rulebook.add /is_(admin|moderator|super_user|user)/ do |title|
|
45
|
+
@title = title.to_sym
|
46
|
+
end
|
53
47
|
end
|
54
48
|
|
55
49
|
You can now do things like
|
56
50
|
|
57
|
-
users = [
|
58
|
-
|
59
|
-
User.new('Natale'),
|
60
|
-
User.new('Joe'),
|
61
|
-
User.new('Monica'),
|
62
|
-
User.new('Matt'),
|
63
|
-
User.new('Jess')
|
64
|
-
].shuffle
|
51
|
+
users = ['Ryan', 'Natale', 'Kasey', 'Jenna', 'Joe', 'Monica','Allan', 'Amanda']
|
52
|
+
users.collect! { |n| User.new(n) }.shuffle!
|
65
53
|
|
66
54
|
users[0].is_admin
|
67
55
|
users[1].is_moderator
|
68
56
|
users[2].is_super_user
|
69
57
|
|
70
58
|
users.each do |user|
|
71
|
-
|
59
|
+
puts "#{user.name} is a #{user.title}"
|
72
60
|
end
|
73
61
|
|
74
62
|
## Class Methods Example
|
@@ -76,26 +64,32 @@ You can now do things like
|
|
76
64
|
require 'rulebook'
|
77
65
|
|
78
66
|
class Car
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
67
|
+
attr :make, :model
|
68
|
+
|
69
|
+
def initialize(make, model)
|
70
|
+
@make, @model = make.capitalize, model.capitalize
|
71
|
+
end
|
72
|
+
|
73
|
+
class << self
|
74
|
+
follows_the_rules!
|
75
|
+
|
76
|
+
rulebook.add /new_([a-z]+)_(.+)/ do |make, model|
|
77
|
+
new(make, model)
|
87
78
|
end
|
79
|
+
end
|
88
80
|
end
|
89
81
|
|
90
82
|
my_cars = [
|
91
|
-
|
92
|
-
|
93
|
-
|
83
|
+
Car.new_honda_accord,
|
84
|
+
Car.new_dodge_neon,
|
85
|
+
Car.new_volkswagen_beetle
|
94
86
|
]
|
95
87
|
|
96
88
|
p my_cars.first.make # => "Honda"
|
97
89
|
p my_cars.first.model # => "Accord"
|
98
90
|
|
91
|
+
This works out if you already have ``
|
92
|
+
|
99
93
|
### Now lets add some instance rules
|
100
94
|
|
101
95
|
class Car
|
data/Rakefile
CHANGED
@@ -1,64 +1,43 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require 'rake'
|
3
2
|
|
4
|
-
|
5
|
-
require 'jeweler'
|
6
|
-
Jeweler::Tasks.new do |gem|
|
7
|
-
gem.name = "rulebook"
|
8
|
-
gem.summary = %Q{Allows you to define a set of 'rules' or dynamic methods to apply to a class}
|
9
|
-
gem.description = %Q{Lets you define methods with regex for dynamic methods}
|
10
|
-
gem.email = "c00lryguy@gmail.com"
|
11
|
-
gem.homepage = "http://github.com/c00lryguy/rulebook"
|
12
|
-
gem.authors = ["Ryan Lewis"]
|
13
|
-
# gem.add_development_dependency "riot", ">= 0"
|
14
|
-
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
-
end
|
16
|
-
Jeweler::GemcutterTasks.new
|
17
|
-
rescue LoadError
|
18
|
-
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
19
|
-
end
|
3
|
+
# Gay as hell jeweler workaround
|
20
4
|
|
21
|
-
require '
|
22
|
-
|
23
|
-
test.libs << 'lib' << 'test'
|
24
|
-
test.pattern = 'test/**/test_*.rb'
|
25
|
-
test.verbose = false
|
26
|
-
end
|
5
|
+
require 'psych'
|
6
|
+
YAML::ENGINE.yamler = 'psych'
|
27
7
|
|
8
|
+
require 'bundler'
|
28
9
|
begin
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
35
|
-
rescue LoadError
|
36
|
-
task :rcov do
|
37
|
-
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
38
|
-
end
|
10
|
+
Bundler.setup(:default, :development)
|
11
|
+
rescue Bundler::BundlerError => e
|
12
|
+
$stderr.puts e.message
|
13
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
14
|
+
exit e.status_code
|
39
15
|
end
|
16
|
+
require 'rake'
|
40
17
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
18
|
+
require 'jeweler'
|
19
|
+
Jeweler::Tasks.new do |gem|
|
20
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
21
|
+
gem.name = "rulebook"
|
22
|
+
gem.homepage = "http://github.com/c00lryguy/rulebook"
|
23
|
+
gem.license = "MIT"
|
24
|
+
gem.summary = %Q{Define methods with regex for dynamic methods.}
|
25
|
+
gem.description = %Q{Allows you to define a set of 'rules' or dynamic methods to apply to a class.}
|
26
|
+
gem.email = "c00lryguy@gmail.com"
|
27
|
+
gem.authors = ["Ryan Lewis"]
|
28
|
+
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
29
|
+
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
30
|
+
gem.add_runtime_dependency 'meta_tools', '> 0.1'
|
31
|
+
gem.add_development_dependency 'rake', '> 0.0.0'
|
32
|
+
gem.add_development_dependency 'riot', '> 0.0.0'
|
53
33
|
end
|
34
|
+
Jeweler::RubygemsDotOrgTasks.new
|
54
35
|
|
36
|
+
require 'rake/testtask'
|
55
37
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
end
|
63
|
-
rescue LoadError
|
64
|
-
end
|
38
|
+
Rake::TestTask.new(:test) do |t|
|
39
|
+
t.libs.concat ['lib', 'test']
|
40
|
+
t.pattern = 'test/**/test_*.rb'
|
41
|
+
t.verbose = false
|
42
|
+
end
|
43
|
+
task :default => :test
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/examples/simple.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
2
|
+
require 'rulebook'
|
3
|
+
|
4
|
+
class User
|
5
|
+
follows_the_rules!
|
6
|
+
|
7
|
+
attr :title
|
8
|
+
|
9
|
+
def initialize(title=:user)
|
10
|
+
@title = title
|
11
|
+
end
|
12
|
+
|
13
|
+
rulebook.add /^is_(admin|user)$/ do |title|
|
14
|
+
@title = title.to_sym
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
rulebook.add /^is_(admin|user)\?$/ do |title|
|
19
|
+
@title == title.to_sym
|
20
|
+
end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
metaclass.follows_the_rules!
|
24
|
+
metaclass.rulebook.add /^new_(admin|user)$/ do |title|
|
25
|
+
instance = new
|
26
|
+
instance.instance_eval { @title = title.to_sym }
|
27
|
+
instance
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
u = User.new
|
33
|
+
|
34
|
+
p u.is_user? # => true
|
35
|
+
p u.is_admin? # => false
|
36
|
+
|
37
|
+
u.is_admin
|
38
|
+
|
39
|
+
p u.is_user? # => false
|
40
|
+
p u.is_admin? # => true
|
41
|
+
|
42
|
+
u = User.new_admin
|
43
|
+
p u.is_admin? # => true
|
data/lib/rulebook.rb
CHANGED
@@ -1,158 +1,19 @@
|
|
1
|
-
|
2
|
-
class Rule
|
3
|
-
attr :block
|
4
|
-
|
5
|
-
def initialize(what_to_capture, &block)
|
6
|
-
raise(TypeError, 'what_to_capture must be of type Regexp') unless what_to_capture.is_a?(Regexp)
|
7
|
-
@what_to_capture, @block = what_to_capture, block
|
8
|
-
end
|
9
|
-
|
10
|
-
def [](query)
|
11
|
-
query.to_s.match(@what_to_capture)
|
12
|
-
end
|
13
|
-
alias_method :match_against, :[]
|
14
|
-
|
15
|
-
def matches_against?(query)
|
16
|
-
!self[query].nil?
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
20
2
|
|
21
|
-
class
|
22
|
-
|
23
|
-
|
24
|
-
def initialize
|
25
|
-
@rules = []
|
26
|
-
end
|
27
|
-
|
28
|
-
def rule(what_to_capture, &block)
|
29
|
-
rule = Rule.new(what_to_capture, &block)
|
30
|
-
@rules << rule
|
31
|
-
rule
|
32
|
-
end
|
33
|
-
|
34
|
-
def rules_that_match_against(regexp)
|
35
|
-
@rules.find_all { |rule| rule.matches_against?(regexp) }
|
36
|
-
end
|
3
|
+
class Rulebook
|
4
|
+
VERSION = "0.4.0"
|
37
5
|
end
|
38
6
|
|
39
|
-
|
40
|
-
class RuleBook
|
41
|
-
module IncludeMethods
|
42
|
-
def respond_to?(meth)
|
43
|
-
rulebook = self.class.const_get('INSTANCE_RULEBOOK')
|
44
|
-
rulebook.rules_that_match_against(meth).any? || super
|
45
|
-
end
|
46
|
-
|
47
|
-
def method_missing(meth, *args, &block)
|
48
|
-
rulebook = self.class.const_get('INSTANCE_RULEBOOK')
|
49
|
-
rules = rulebook.rules_that_match_against(meth)
|
50
|
-
|
51
|
-
# TODO: Why would this ever be nil?
|
52
|
-
unless rules.nil? || rules.empty?
|
53
|
-
# Run the first matched rule..
|
54
|
-
# TODO: if the method NEXT if called within the rule,
|
55
|
-
# then goto the next matched rule
|
56
|
-
rule = rules.first
|
57
|
-
captures = rule[meth].captures || []
|
58
|
-
block = rule.block
|
59
|
-
|
60
|
-
# Remove the possibility of optional arguments
|
61
|
-
arity = block.arity == -1 ? 0 : block.arity
|
62
|
-
|
63
|
-
# Define the method
|
64
|
-
klass = self.class
|
65
|
-
klass.send(:define_method, meth) do |*args|
|
66
|
-
instance_exec(*(captures + args).take(arity), &block)
|
67
|
-
end
|
68
|
-
|
69
|
-
# Call the method
|
70
|
-
send(meth, *args, &block)
|
71
|
-
else
|
72
|
-
super
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
module ExtendMethods
|
78
|
-
def respond_to?(meth)
|
79
|
-
rulebook = const_get('CLASS_NOTEBOOK')
|
80
|
-
rulebook.rules_that_match_against(meth).any? || super
|
81
|
-
end
|
82
|
-
|
83
|
-
def method_missing(meth, *args, &block)
|
84
|
-
rulebook = const_get('CLASS_NOTEBOOK')
|
85
|
-
rules = rulebook.rules_that_match_against(meth)
|
86
|
-
|
87
|
-
# TODO: Why would this ever be nil?
|
88
|
-
unless rules.nil? || rules.empty?
|
89
|
-
# Run the first matched rule..
|
90
|
-
# TODO: if the method NEXT if called within the rule,
|
91
|
-
# then goto the next matched rule
|
92
|
-
rule = rules.first
|
93
|
-
captures = rule[meth].captures || []
|
94
|
-
block = rule.block
|
95
|
-
|
96
|
-
# Remove the possibility of optional arguments
|
97
|
-
arity = block.arity == -1 ? 0 : block.arity
|
98
|
-
|
99
|
-
# Define the method
|
100
|
-
klass = class << self; self; end
|
101
|
-
klass.send(:define_method, meth) do |*args|
|
102
|
-
class_exec(*(captures + args).take(arity), &block)
|
103
|
-
end
|
104
|
-
|
105
|
-
# Call the method
|
106
|
-
send(meth, *args, &block)
|
107
|
-
else
|
108
|
-
super
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
7
|
+
require 'rulebook/rule'
|
113
8
|
|
114
|
-
|
115
|
-
|
116
|
-
def
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
const_get('INSTANCE_RULEBOOK').rule(what_to_capture, &block)
|
121
|
-
end
|
122
|
-
|
123
|
-
def class_rule(what_to_capture, &block)
|
124
|
-
raise(ArgumentError, 'class_rules must have a block') unless block_given?
|
125
|
-
|
126
|
-
setup_rulebook('CLASS_NOTEBOOK', :extend)
|
127
|
-
const_get('CLASS_NOTEBOOK').rule(what_to_capture, &block)
|
128
|
-
end
|
129
|
-
|
130
|
-
def rules(&block)
|
131
|
-
raise(ArgumentError, 'rules must have a block') unless block_given?
|
132
|
-
|
133
|
-
setup_rulebook('INSTANCE_RULEBOOK', :include)
|
134
|
-
const_get('INSTANCE_RULEBOOK').instance_eval(&block)
|
135
|
-
const_get('INSTANCE_RULEBOOK')
|
136
|
-
end
|
137
|
-
|
138
|
-
def class_rules(&block)
|
139
|
-
raise(ArgumentError, 'class_rules must have a block') unless block_given?
|
140
|
-
|
141
|
-
setup_rulebook('CLASS_NOTEBOOK', :extend)
|
142
|
-
const_get('CLASS_NOTEBOOK').instance_eval(&block)
|
143
|
-
const_get('CLASS_NOTEBOOK')
|
144
|
-
end
|
145
|
-
|
146
|
-
private
|
147
|
-
|
148
|
-
def setup_rulebook(rulebook_constant, extend_or_include)
|
149
|
-
raise(ArgumentError, 'extend_or_include must be :extend or :include') unless [:extend, :include].include?(extend_or_include)
|
150
|
-
|
151
|
-
unless const_defined?(rulebook_constant)
|
152
|
-
const_set(rulebook_constant, RuleBook.new)
|
153
|
-
|
154
|
-
module_name = extend_or_include.to_s.capitalize + 'Methods'
|
155
|
-
send(extend_or_include, RuleBook.const_get(module_name))
|
156
|
-
end
|
157
|
-
end
|
9
|
+
class Rulebook
|
10
|
+
attr_accessor :rules
|
11
|
+
def initialize; @rules = []; end
|
12
|
+
def add(what_to_capture, &block); @rules << Rule.new(what_to_capture, &block); end
|
13
|
+
def [](query); @rules.find_all { |rule| rule.matches_against?(query) }; end
|
14
|
+
alias_method :rules_that_match_against, :[]
|
158
15
|
end
|
16
|
+
|
17
|
+
require 'rulebook/class_methods'
|
18
|
+
require 'rulebook/instance_methods'
|
19
|
+
require 'rulebook/core_ext/module'
|