argible 0.1.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/README +90 -0
- data/init.rb +3 -0
- data/lib/argible.rb +143 -0
- data/spec/argible_spec.rb +140 -0
- data/spec/spec_helper.rb +4 -0
- metadata +58 -0
data/README
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
= ARGIBLE
|
2
|
+
|
3
|
+
by {Michael Ward}[http://m2ward.blogspot.com]
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
A gem that allows for automatic resolution of a methods argument values.
|
8
|
+
|
9
|
+
== SYNOPSIS:
|
10
|
+
|
11
|
+
Argible was created to simplify Actions within your Ruby on Rails Controllers. Argible annotated action methods will
|
12
|
+
allow your actions to define argument. These argument names will be used in conjunction with request parameters to
|
13
|
+
automatically resolve of these argument values.
|
14
|
+
|
15
|
+
== INSTALL:
|
16
|
+
|
17
|
+
You can download Argible from here[http://rubyforge.org/projects/argible] or install it with the following command.
|
18
|
+
|
19
|
+
<tt>$ gem install argible</tt>
|
20
|
+
|
21
|
+
To use as a Rails plug-in unpack the gem into your applications <tt>vendor/plugin</tt> directory:
|
22
|
+
|
23
|
+
<tt>$ gem unpack argible</tt>
|
24
|
+
|
25
|
+
== USAGE:
|
26
|
+
|
27
|
+
Have a look at the following Controller:
|
28
|
+
|
29
|
+
class FoobarController < ApplicationController
|
30
|
+
|
31
|
+
argible
|
32
|
+
def action_one(alpha)
|
33
|
+
...
|
34
|
+
end
|
35
|
+
|
36
|
+
argible(:date => Date.method(:parse))
|
37
|
+
def action_two(date)
|
38
|
+
...
|
39
|
+
end
|
40
|
+
|
41
|
+
argible(:date => lambda {|v| Date.parse(v) } )
|
42
|
+
def action_three(date)
|
43
|
+
...
|
44
|
+
end
|
45
|
+
|
46
|
+
argible(:value => :to_i)
|
47
|
+
def action_four(value)
|
48
|
+
...
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
The first method *action_one* is annotated with the method *argible*. When the action_one action method is executed
|
53
|
+
Argible will intercept the call and look up the request parameter value associated with *alpha*. Then action_one will
|
54
|
+
be called with this resolved value.
|
55
|
+
|
56
|
+
The second action *action_two* provides an example of how more complex processing can occur on argument values. As in
|
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.
|
60
|
+
|
61
|
+
The action *action_three* provides an example of using a Proc (lambda) as alternative to a named method.
|
62
|
+
|
63
|
+
The fourth example action *action_four* illustrates how you can call methods directly on the String value returned from
|
64
|
+
the request parameter
|
65
|
+
|
66
|
+
|
67
|
+
== LICENSE:
|
68
|
+
|
69
|
+
(The MIT License)
|
70
|
+
|
71
|
+
Copyright (c) 2007 Michael Ward
|
72
|
+
|
73
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
74
|
+
a copy of this software and associated documentation files (the
|
75
|
+
'Software'), to deal in the Software without restriction, including
|
76
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
77
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
78
|
+
permit persons to whom the Software is furnished to do so, subject to
|
79
|
+
the following conditions:
|
80
|
+
|
81
|
+
The above copyright notice and this permission notice shall be
|
82
|
+
included in all copies or substantial portions of the Software.
|
83
|
+
|
84
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
85
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
86
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
87
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
88
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
89
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
90
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/init.rb
ADDED
data/lib/argible.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'parse_tree'
|
2
|
+
|
3
|
+
module Argible
|
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
|
31
|
+
|
32
|
+
class UnknownArgumentError < ArgumentError
|
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
|
39
|
+
|
40
|
+
def self.included(base)
|
41
|
+
base.extend(ClassMethods) # Adding class methods
|
42
|
+
base.send(:include, InstanceMethods)
|
43
|
+
end
|
44
|
+
|
45
|
+
module ClassMethods
|
46
|
+
|
47
|
+
# Used to annotate methods that should have their argument values automatically resolved
|
48
|
+
#
|
49
|
+
# argible
|
50
|
+
# def annotated_method(foo, bar)
|
51
|
+
# ...
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
def argible(options = {})
|
55
|
+
@method_definition = MethodDefinition.new(options)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Callback to alert Argible of the method being annotated
|
59
|
+
def method_added(method_name)
|
60
|
+
|
61
|
+
unless @method_definition.nil?
|
62
|
+
@method_definition.name = method_name
|
63
|
+
@method_definition.arguments = get_arguments(method_name)
|
64
|
+
|
65
|
+
# Add class instance variable 'argible_methods' if it does not already exist
|
66
|
+
unless self.respond_to?(:argible_methods)
|
67
|
+
class << self; attr_accessor :argible_methods; end
|
68
|
+
self.instance_variable_set(:@argible_methods, Hash.new)
|
69
|
+
end
|
70
|
+
|
71
|
+
self.argible_methods[method_name] = @method_definition
|
72
|
+
|
73
|
+
@method_definition = nil # clearing to prevent infinite loop
|
74
|
+
|
75
|
+
build_alias_method(method_name)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def get_arguments(method_name)
|
82
|
+
ParseTree.new.parse_tree_for_method(self, method_name)[2][1][1][1..-1]
|
83
|
+
end
|
84
|
+
|
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
|
+
def build_alias_method(method_name)
|
98
|
+
original_method = "__argible_#{method_name}".to_sym
|
99
|
+
alias_method(original_method, method_name)
|
100
|
+
|
101
|
+
# define the proxying method
|
102
|
+
define_method(method_name) do
|
103
|
+
method_definition = self.class.argible_methods[method_name]
|
104
|
+
arguments = {}
|
105
|
+
|
106
|
+
method_definition.arguments.collect do |name|
|
107
|
+
arguments[name] = ArgumentResolver.resolve(self, name)
|
108
|
+
end
|
109
|
+
|
110
|
+
process_argible_options(method_definition, arguments)
|
111
|
+
|
112
|
+
self.send(original_method, *arguments.values)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
module InstanceMethods
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
# responsible for resolving argument value
|
123
|
+
def resolve_argument(name)
|
124
|
+
return params[name.to_s]
|
125
|
+
end
|
126
|
+
|
127
|
+
def process_argible_options(method_definition, arguments)
|
128
|
+
method_definition.options.each do |key, value|
|
129
|
+
unless arguments.include?(key) # only process key if it exists in the method argument list
|
130
|
+
raise UnknownArgumentError.new(self.class, method_definition.name, key)
|
131
|
+
end
|
132
|
+
|
133
|
+
if value.is_a?(Method) or value.is_a?(Proc)
|
134
|
+
arguments[key] = value.call(arguments[key])
|
135
|
+
elsif value.is_a?(Symbol)
|
136
|
+
arguments[key] = arguments[key].send(value)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe ::Argible, " method added callback" do
|
4
|
+
|
5
|
+
class FooBar
|
6
|
+
include ::Argible
|
7
|
+
|
8
|
+
argible
|
9
|
+
def alpha(beta, gamma); end
|
10
|
+
|
11
|
+
argible(:junk => :to_i)
|
12
|
+
def no_arg_method; end
|
13
|
+
|
14
|
+
def not_annotated; end
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should only cache argible annotated methods" do
|
18
|
+
FooBar.argible_methods.should include(:alpha)
|
19
|
+
FooBar.argible_methods.should_not include(:not_annotated)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should be able to determine each argument by name (Symbol)" do
|
23
|
+
arguments = FooBar.argible_methods[:alpha].arguments
|
24
|
+
arguments[0].should == :beta
|
25
|
+
arguments[1].should == :gamma
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should create alias for annotated methods" do
|
29
|
+
FooBar.instance_methods.should include('__argible_alpha')
|
30
|
+
FooBar.instance_methods.should_not include('__argible_not_annotated')
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should raise error when argible options reference an unknown argument" do
|
34
|
+
FooBar.argible_methods[:no_arg_method].arguments.length.should == 0
|
35
|
+
|
36
|
+
begin
|
37
|
+
FooBar.new.no_arg_method
|
38
|
+
fail("UnknownArgumentError should have been raised")
|
39
|
+
rescue ::Argible::UnknownArgumentError => e
|
40
|
+
e.message.should == "FooBar#no_arg_method' does not include an argument named: junk"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should add argible_methods instance variable to child classes" do
|
45
|
+
class ArgibleizedParent
|
46
|
+
include ::Argible
|
47
|
+
end
|
48
|
+
|
49
|
+
class Child < ArgibleizedParent
|
50
|
+
|
51
|
+
argible
|
52
|
+
def method_one; end
|
53
|
+
end
|
54
|
+
|
55
|
+
Child.should respond_to(:argible_methods)
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
describe ::Argible, "proxied methods" do
|
61
|
+
|
62
|
+
it "should resolve argument name from params hash (RoR)" do
|
63
|
+
class Zoo # Class under test
|
64
|
+
include ::Argible
|
65
|
+
|
66
|
+
attr_reader :animals, :price
|
67
|
+
|
68
|
+
def initialize; @animals = []; end
|
69
|
+
|
70
|
+
argible
|
71
|
+
def add(animal)
|
72
|
+
@animals << animal
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
zoo = Zoo.new
|
77
|
+
zoo.should_receive(:params).and_return({'animal' => 'Grizzly Bear'})
|
78
|
+
|
79
|
+
zoo.add # calling the argible-ized method
|
80
|
+
zoo.animals.should include('Grizzly Bear')
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should be able to delegate to referenced Method" do
|
84
|
+
|
85
|
+
class Person
|
86
|
+
include ::Argible
|
87
|
+
|
88
|
+
attr_reader :dob
|
89
|
+
|
90
|
+
argible(:date => Date.method(:parse))
|
91
|
+
def birth_date(date)
|
92
|
+
@dob = date
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
person = Person.new
|
98
|
+
person.should_receive(:params).and_return('date' => "1-2-2007")
|
99
|
+
person.birth_date
|
100
|
+
|
101
|
+
person.dob.should == Date.civil(2007, 2, 1)
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should be able to delegate to Proc (lambda)" do
|
105
|
+
|
106
|
+
class FooBar
|
107
|
+
|
108
|
+
argible(:number => lambda{|v| v.to_i * 10})
|
109
|
+
def calculate(number)
|
110
|
+
return number
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
foobar = FooBar.new
|
115
|
+
foobar.should_receive(:params).and_return('number' => "99")
|
116
|
+
foobar.calculate.should == 990
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should be able to call method directly on resolved value" do
|
120
|
+
class FooBar
|
121
|
+
|
122
|
+
argible(:value => :to_i)
|
123
|
+
def echo(value)
|
124
|
+
return value
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
foobar = FooBar.new
|
129
|
+
foobar.should_receive(:params).and_return('value' => "1985")
|
130
|
+
foobar.echo.should == 1985
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
# TODO might look at supporting arguments:
|
137
|
+
# :four => :@four, # set instance variable
|
138
|
+
|
139
|
+
# TODO might want to handle argument default values (i.e. foo(bar=99, baz={}) )
|
140
|
+
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.4
|
3
|
+
specification_version: 1
|
4
|
+
name: argible
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.1.0
|
7
|
+
date: 2007-08-20 00:00:00 -05:00
|
8
|
+
summary: automatically resolves method argument values
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: argible-developer@rubyforge.org
|
12
|
+
homepage: http://argible.rubyforge.org
|
13
|
+
rubyforge_project: argible
|
14
|
+
description:
|
15
|
+
autorequire: argible
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Michael Ward
|
31
|
+
files:
|
32
|
+
- lib/argible.rb
|
33
|
+
- spec/argible_spec.rb
|
34
|
+
- spec/spec_helper.rb
|
35
|
+
- init.rb
|
36
|
+
- README
|
37
|
+
test_files: []
|
38
|
+
|
39
|
+
rdoc_options: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- README
|
43
|
+
executables: []
|
44
|
+
|
45
|
+
extensions: []
|
46
|
+
|
47
|
+
requirements: []
|
48
|
+
|
49
|
+
dependencies:
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: ParseTree
|
52
|
+
version_requirement:
|
53
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 2.0.0
|
58
|
+
version:
|