argible 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +29 -17
- data/lib/argible.rb +23 -60
- data/lib/argible/argument_resolver.rb +24 -0
- data/lib/argible/method_definition.rb +17 -0
- data/lib/argible/unknown_argument_error.rb +17 -0
- data/lib/argible/version.rb +8 -0
- data/spec/argible_spec.rb +16 -3
- metadata +6 -2
data/README
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
= ARGIBLE
|
1
|
+
= ARGIBLE: A dynamic method argument resolver for Ruby
|
2
2
|
|
3
3
|
by {Michael Ward}[http://m2ward.blogspot.com]
|
4
4
|
|
@@ -29,39 +29,51 @@ Have a look at the following Controller:
|
|
29
29
|
class FoobarController < ApplicationController
|
30
30
|
|
31
31
|
argible
|
32
|
-
def action_one(alpha)
|
32
|
+
def action_one(alpha, beta)
|
33
33
|
...
|
34
34
|
end
|
35
35
|
|
36
|
-
argible(:date => Date.method(:parse))
|
37
|
-
def action_two(date)
|
36
|
+
argible(:date => Date.method(:parse)) # process argument value for 'date' by passing it to Date#parse
|
37
|
+
def action_two(alpha, date)
|
38
38
|
...
|
39
39
|
end
|
40
40
|
|
41
|
-
argible(:date => lambda {|v| Date.parse(v) } )
|
41
|
+
argible(:date => lambda {|v| Date.parse(v) } ) # process argument value for 'date' by passing it to the Proc
|
42
42
|
def action_three(date)
|
43
43
|
...
|
44
44
|
end
|
45
45
|
|
46
|
-
argible(:
|
47
|
-
def action_four(
|
46
|
+
argible(:count => :to_i) # invoke the :to_i method on the resolved value for 'count'
|
47
|
+
def action_four(count)
|
48
|
+
...
|
49
|
+
end
|
50
|
+
|
51
|
+
argible(:the_name => :@first_name) # resolves the value for 'the_name' and sets it to the instance variable @first_name
|
52
|
+
def action_five
|
53
|
+
p @first_name # this instance variable has been set automatically by Argible
|
48
54
|
...
|
49
55
|
end
|
50
56
|
end
|
51
57
|
|
52
|
-
The first method *action_one
|
53
|
-
Argible will intercept the call and look up the request parameter value associated with
|
54
|
-
be called with
|
58
|
+
The first method, *action_one*, is annotated with the method +argible+. When this action method is executed
|
59
|
+
Argible will intercept the call and look up the request parameter value associated with the argument names +alpha+ and
|
60
|
+
+beta+. Then the action_one method will be called with these resolved values.
|
61
|
+
|
62
|
+
The second action, *action_two*, provides an example of how more complex processing can occur on argument
|
63
|
+
values. As in the first example the both arguments, +alpha+ and +date+, will be resolved against the request parameters.
|
64
|
+
However the resolved value for +date+ will then be passed to the <tt>Date#parse</tt> method. The returned value from
|
65
|
+
<tt>Date#parse</tt> method call will then be used as the argument value for +date+ when +action_two+ is called. **NOTE:** any
|
66
|
+
method can be used, Date#parse is simply used as an example.
|
55
67
|
|
56
|
-
The
|
57
|
-
the first example the argument *date* is resolved against the request parameters. This value will then be passed to the
|
58
|
-
Date#parse method. The result from Date#parse will then be used when *action_two* is called. **NOTE:** any method can
|
59
|
-
be used, Date#parse is simply used as an example.
|
68
|
+
The third example action, *action_three*, provides an example of using a Proc (lambda) as alternative to a named method.
|
60
69
|
|
61
|
-
The action *
|
70
|
+
The fourth example action, *action_four*, illustrates how you can call methods directly on the String value
|
71
|
+
returned from the request parameter. Assume request parameter hash was <tt>{'count' => "1985"}</tt> then the Integer value
|
72
|
+
+1985+ would be passed as the argument value for the argument named <tt>'count'</tt>.
|
62
73
|
|
63
|
-
The
|
64
|
-
the
|
74
|
+
The final example action, *action_five*, demonstrates how you can automatically set the value of an instance variable
|
75
|
+
without requiring a method argument of the same name. This example works because <tt>:my_name</tt> points to a symbol
|
76
|
+
begining with a '@' character. Without this '@' on the right hand side Argible would raise a Argible::UnknownArgumentError.
|
65
77
|
|
66
78
|
|
67
79
|
== LICENSE:
|
data/lib/argible.rb
CHANGED
@@ -1,41 +1,8 @@
|
|
1
1
|
require 'parse_tree'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
# defines information about the method being annotated
|
6
|
-
class MethodDefinition
|
7
|
-
attr_accessor :name, :options
|
8
|
-
attr_reader :arguments
|
9
|
-
|
10
|
-
def initialize(options)
|
11
|
-
@options = options
|
12
|
-
end
|
13
|
-
|
14
|
-
def arguments=(arguments)
|
15
|
-
@arguments = arguments.select { |arg| arg.is_a?(Symbol) } # Only grab argument names
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
class ArgumentResolver
|
20
|
-
class << self; attr_accessor :resolver; end
|
21
|
-
|
22
|
-
# By default resolves argument values from request parameters
|
23
|
-
@resolver = lambda do |object, argument_name|
|
24
|
-
return object.send(:params)[argument_name.to_s]
|
25
|
-
end
|
26
|
-
|
27
|
-
def self.resolve(object, argument_name)
|
28
|
-
return @resolver.call(object, argument_name)
|
29
|
-
end
|
30
|
-
end
|
3
|
+
Dir[File.join(File.dirname(__FILE__), 'argible/**/*.rb')].sort.each { |lib| require lib }
|
31
4
|
|
32
|
-
|
33
|
-
|
34
|
-
def initialize(class_name, method_name, key)
|
35
|
-
super("#{class_name}##{method_name}' does not include an argument named: #{key}" )
|
36
|
-
end
|
37
|
-
|
38
|
-
end
|
5
|
+
module Argible
|
39
6
|
|
40
7
|
def self.included(base)
|
41
8
|
base.extend(ClassMethods) # Adding class methods
|
@@ -51,11 +18,20 @@ module Argible
|
|
51
18
|
# ...
|
52
19
|
# end
|
53
20
|
#
|
21
|
+
# The +options+ Hash passed in can define custom processing to occur on a per argument basis. For example:
|
22
|
+
#
|
23
|
+
# argible(
|
24
|
+
# :one => :to_i, # call method 'to_i' on the argument value
|
25
|
+
# :two => lambda{ |value| return value }, # defines a Proc to run (resolved argumnet value passed to Proc)
|
26
|
+
# :three => method(:meth_name), # method to call (resolved argumnet value passed to Method)
|
27
|
+
# :four => :@four # argument value is set to the instance variable of name (i.e. @four)
|
28
|
+
# )
|
29
|
+
#
|
54
30
|
def argible(options = {})
|
55
31
|
@method_definition = MethodDefinition.new(options)
|
56
32
|
end
|
57
33
|
|
58
|
-
#
|
34
|
+
# This callback is used by Argible to determine which methods have been annotated with the argible() method.
|
59
35
|
def method_added(method_name)
|
60
36
|
|
61
37
|
unless @method_definition.nil?
|
@@ -82,18 +58,6 @@ module Argible
|
|
82
58
|
ParseTree.new.parse_tree_for_method(self, method_name)[2][1][1][1..-1]
|
83
59
|
end
|
84
60
|
|
85
|
-
# 1. Determine all arguments
|
86
|
-
# 1. Resolve argument values
|
87
|
-
# 2. Process arguments
|
88
|
-
# A. method to call on value
|
89
|
-
# B. pass to Proc
|
90
|
-
# B. pass to Method
|
91
|
-
#
|
92
|
-
# argible(
|
93
|
-
# :one => :to_i, # call method on argument value
|
94
|
-
# :two => lambda{ |value| return value }, # Proc to run
|
95
|
-
# :three => method(:alt_name) # method to call
|
96
|
-
#)
|
97
61
|
def build_alias_method(method_name)
|
98
62
|
original_method = "__argible_#{method_name}".to_sym
|
99
63
|
alias_method(original_method, method_name)
|
@@ -119,21 +83,20 @@ module Argible
|
|
119
83
|
|
120
84
|
private
|
121
85
|
|
122
|
-
# responsible for resolving argument value
|
123
|
-
def resolve_argument(name)
|
124
|
-
return params[name.to_s]
|
125
|
-
end
|
126
|
-
|
127
86
|
def process_argible_options(method_definition, arguments)
|
128
87
|
method_definition.options.each do |key, value|
|
129
88
|
unless arguments.include?(key) # only process key if it exists in the method argument list
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
89
|
+
if(value.is_a?(Symbol) and value.to_s =~ /^@/)
|
90
|
+
self.instance_variable_set(value, ArgumentResolver.resolve(self, key)) # set instance variable
|
91
|
+
else
|
92
|
+
raise UnknownArgumentError.new(self.class, method_definition.name, key)
|
93
|
+
end
|
94
|
+
else
|
95
|
+
if value.is_a?(Method) or value.is_a?(Proc)
|
96
|
+
arguments[key] = value.call(arguments[key])
|
97
|
+
elsif value.is_a?(Symbol) # method to be invoked on value
|
98
|
+
arguments[key] = arguments[key].send(value)
|
99
|
+
end
|
137
100
|
end
|
138
101
|
end
|
139
102
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Argible
|
2
|
+
|
3
|
+
# This class defines the method/proc that will be called to resolve an arguments value. By default this
|
4
|
+
# implementation will resolve an arguments value through RoR's request parameters. It is possible to plug-in an
|
5
|
+
# alternate resolver implementation like so (in the example all arguments values will resolve to the value 'foobar'):
|
6
|
+
#
|
7
|
+
# ::Argible::ArgumentResolver.resolver = lambda do |instance, argument_name|
|
8
|
+
# return "foobar"
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
class ArgumentResolver
|
12
|
+
class << self; attr_accessor :resolver; end
|
13
|
+
|
14
|
+
# By default resolves argument values from request parameters
|
15
|
+
@resolver = lambda do |object, argument_name|
|
16
|
+
return object.send(:params)[argument_name.to_s]
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.resolve(object, argument_name)
|
20
|
+
return @resolver.call(object, argument_name)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Argible
|
2
|
+
|
3
|
+
# defines information about the method being annotated
|
4
|
+
class MethodDefinition # :nodoc:
|
5
|
+
attr_accessor :name, :options
|
6
|
+
attr_reader :arguments
|
7
|
+
|
8
|
+
def initialize(options)
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def arguments=(arguments)
|
13
|
+
@arguments = arguments.select { |arg| arg.is_a?(Symbol) } # Only grab argument names
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Argible
|
2
|
+
|
3
|
+
# As the name implies, this error occurs when an argible annotated method references an argument name that does NOT
|
4
|
+
# exist in the method signature.
|
5
|
+
#
|
6
|
+
# argible(:junk => :to_i)
|
7
|
+
# def my_method(arg1, arg2); end;
|
8
|
+
#
|
9
|
+
class UnknownArgumentError < ArgumentError
|
10
|
+
|
11
|
+
def initialize(class_name, method_name, key)
|
12
|
+
super("#{class_name}##{method_name}' does not include an argument named: #{key}" )
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/spec/argible_spec.rb
CHANGED
@@ -130,11 +130,24 @@ describe ::Argible, "proxied methods" do
|
|
130
130
|
foobar.echo.should == 1985
|
131
131
|
end
|
132
132
|
|
133
|
-
|
133
|
+
it "should be able to directly set instance variable (even without associated argument name)" do
|
134
|
+
|
135
|
+
class FooBar
|
136
|
+
|
137
|
+
argible(:name => :@name)
|
138
|
+
def change_name
|
139
|
+
return @name
|
140
|
+
end
|
141
|
+
end
|
134
142
|
|
143
|
+
foobar = FooBar.new
|
144
|
+
foobar.should_receive(:params).and_return('name' => "Regan")
|
135
145
|
|
136
|
-
|
137
|
-
|
146
|
+
foobar.change_name
|
147
|
+
foobar.instance_variable_get(:@name).should == "Regan" # instance variable set correctly
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
138
151
|
|
139
152
|
# TODO might want to handle argument default values (i.e. foo(bar=99, baz={}) )
|
140
153
|
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.4
|
|
3
3
|
specification_version: 1
|
4
4
|
name: argible
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.1.
|
7
|
-
date: 2007-08-
|
6
|
+
version: 0.1.1
|
7
|
+
date: 2007-08-22 00:00:00 -05:00
|
8
8
|
summary: automatically resolves method argument values
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -29,6 +29,10 @@ post_install_message:
|
|
29
29
|
authors:
|
30
30
|
- Michael Ward
|
31
31
|
files:
|
32
|
+
- lib/argible/argument_resolver.rb
|
33
|
+
- lib/argible/method_definition.rb
|
34
|
+
- lib/argible/unknown_argument_error.rb
|
35
|
+
- lib/argible/version.rb
|
32
36
|
- lib/argible.rb
|
33
37
|
- spec/argible_spec.rb
|
34
38
|
- spec/spec_helper.rb
|