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 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
@@ -0,0 +1,3 @@
1
+ require 'argible'
2
+
3
+ ActionController::Base.send(:include, ::Argible)
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
+
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+
4
+ require File.dirname(__FILE__) + '/../lib/argible'
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: