invokr 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d0174897be54196672b5fc187801b8afb7b950a5
4
+ data.tar.gz: 4b3b4c3656ffb373aab0cfdd56060689dbb7a40b
5
+ SHA512:
6
+ metadata.gz: 0f5edb40a999afc101c009ce040a82170c3356ffe25b79150e590dc7b6813ac84db2d9be765ac74764c8f0102cd57cc00a02ae1376fa776dd6e593b1865a106e
7
+ data.tar.gz: 4c8001a382bb203d173ef4558f690678987c9ad9d5113f7e0dbad775e0e24748b291e057ac7f2abbb29f47cc9f9ca99d7a2c68dfd3fce05f658c5dac401d2cc6
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ tags
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in invokr.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 ntl
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,69 @@
1
+ # Invokr
2
+
3
+ Invoke methods with a consistent Hash interface. Useful for metaprogramming.
4
+
5
+ ## Basic Usage
6
+
7
+ Let's say you've got a method you want to call:
8
+
9
+ ```ruby
10
+ class BankAccount
11
+ def add_transaction(amount, account_id:, description: '')
12
+ # adds transaction
13
+ end
14
+ end
15
+ ```
16
+
17
+ You can invoke this method through the class interface with pure ruby. But sometimes you've got a Hash containing the parameters. This is where Invokr comes in:
18
+
19
+ ```ruby
20
+ bank_account = BankAccount.new
21
+ params = JSON.parse http_request.response_body
22
+ Invokr.invoke method: :add_transaction, on: bank_account, with: params
23
+ ```
24
+
25
+ Behind the scenes, Invokr figured out how to translate that `Hash` into a method signature compatible with `BankAccount#add_transaction`.
26
+
27
+ ## Querying
28
+
29
+ Want to investigate the arguments of a method?
30
+
31
+ ```ruby
32
+ meth = Invokr.query_method bank_account.method(:add_transaction)
33
+ ```
34
+
35
+ This will return an object that you can use to inspect the optional/required dependencies of a method:
36
+
37
+ ```ruby
38
+ meth.required_dependencies
39
+ => [:amount, :account_id]
40
+ meth.optional_dependencies
41
+ => [:description]
42
+ ```
43
+
44
+ ## Limitations
45
+
46
+ Currently, more than one optional positional argument isn't supported. Consider:
47
+
48
+ ```ruby
49
+ def my_method arg1 = 'foo', arg2 = 'bar'
50
+ end
51
+ ```
52
+
53
+ Without knowing how to parse the source code for `#my_method`, Invokr couldn't know what the default values are. And even if I brought in e.g. [ruby_parser](https://github.com/seattlerb/ruby_parser), I'd have to support lazy evaluation, for when you supply a method or constant as the default. This complexity is completely unneccessary when using keyword arguments, so I suggest using that approach for multiple defaults.
54
+
55
+ ## Pre-keyword argument hash defaults
56
+
57
+ Before ruby 2.x introduced keyword arguments, it was common to end your method signature with a default hash, e.g. `def my_method args = {}`. Invoker supports this by building a Hash out of all the unused arguments you passed in, and passing *that* into the optional argument.
58
+
59
+ ## Dependency injection
60
+
61
+ One of the use cases for Invokr is building abstract factories. In this case, you want to inspect the method signature of `Object#initialize`, but actually pass `.new` to the class to have it allocate memory and invoke the initializer for you. Since this is a weird case, there's some support to make building a dependency injector much easier, just make sure you explictily `require "invokr/dependency_injection"` and then check out `test/dependency_injection_example_test.rb` for how it is used. Basically, your factory object just needs to implement a method called `resolve` that takes in the name of a dependency that maps to a parameter on the `#initialize` method for the class you're trying to build out.
62
+
63
+ ## Contributing
64
+
65
+ 1. Fork it ( https://github.com/[my-github-username]/invokr/fork )
66
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
67
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
68
+ 4. Push to the branch (`git push origin my-new-feature`)
69
+ 5. Create a new Pull Request
@@ -0,0 +1,22 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ desc "Update ctags"
4
+ task :ctags do
5
+ `ctags -R --languages=Ruby --totals -f tags`
6
+ end
7
+
8
+ desc "Test against all ruby versions"
9
+ task :rbenv_suite do
10
+ %w(2.1.2 2.0.0-p481 1.9.3-p545).each do |ruby_ver|
11
+ child_pid = fork do
12
+ ruby_path = File.join ENV['RBENV_ROOT'], 'shims/ruby'
13
+ cmd = "#{ruby_path} bin/test_runner ; echo"
14
+ puts "Testing #{ruby_ver}"
15
+ env = { 'RBENV_VERSION' => ruby_ver }
16
+ exec env, cmd
17
+ end
18
+ Process.wait2 child_pid
19
+ end
20
+ end
21
+
22
+ task :default => :rbenv_suite
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'rake' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ load "bin/test_runner" and exit 0 if ARGV.empty?
10
+
11
+ require 'pathname'
12
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ require 'rubygems'
16
+ require 'bundler/setup'
17
+
18
+ load Gem.bin_path('rake', 'rake')
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Invokr
4
+ module Runner
5
+ extend self
6
+
7
+ def run!
8
+ root = File.expand_path '../..', __FILE__
9
+
10
+ load File.join(root, 'test/test_helper.rb')
11
+
12
+ Dir[File.join(root, 'test/**/*_test.rb')].each do |test_file|
13
+ load test_file
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ Invokr::Runner.run!
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'invokr/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "invokr"
8
+ spec.version = Invokr::VERSION
9
+ spec.authors = %w(ntl)
10
+ spec.email = %w(nathanladd+github@gmail.com)
11
+ spec.summary = %q{Invoke methods with a consistent Hash interface.}
12
+ spec.description = %q{Invoke methods with a consistent Hash interface. Useful for metaprogramming.}
13
+ spec.homepage = "https://github.com/ntl/invokr"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = []
18
+ spec.test_files = spec.files.grep %r{test/}
19
+ spec.require_paths = %w(lib)
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "minitest"
23
+ spec.add_development_dependency "minitest-reporters"
24
+ spec.add_development_dependency "rake"
25
+ end
@@ -0,0 +1,57 @@
1
+ module Invokr
2
+ extend self
3
+
4
+ def invoke args = {}
5
+ method_name, obj, hsh_args = require_arguments! args, :method, :on, :with
6
+ method = obj.method method_name
7
+ invocation = Builder.build method, hsh_args, args[:block]
8
+ invocation.invoke! obj
9
+ end
10
+
11
+ def query_method method
12
+ Method.new method
13
+ end
14
+
15
+ Method = Struct.new :method do
16
+ def dependencies
17
+ map_identifiers parameters
18
+ end
19
+
20
+ def optional_dependencies
21
+ map_identifiers select_parameters_by_type [:opt, :key]
22
+ end
23
+
24
+ def required_dependencies
25
+ map_identifiers select_parameters_by_type [:req, :keyreq]
26
+ end
27
+
28
+ def parameters
29
+ method.parameters
30
+ end
31
+
32
+ private
33
+
34
+ def select_parameters_by_type types
35
+ parameters.select do |type, _| types.include? type end
36
+ end
37
+
38
+ def map_identifiers parameters
39
+ parameters.map do |_, identifier| identifier end
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def require_arguments! hsh, *args
46
+ found_args, missing_args = args.partition do |arg|
47
+ hsh.has_key? arg
48
+ end
49
+ raise InputError.new missing_args unless missing_args.empty?
50
+ found_args.map { |arg| hsh.fetch arg }
51
+ end
52
+ end
53
+
54
+ require_relative 'invokr/builder'
55
+ require_relative 'invokr/errors'
56
+ require_relative 'invokr/invocation'
57
+ require_relative 'invokr/version'
@@ -0,0 +1,116 @@
1
+ module Invokr
2
+ class Builder
3
+ def self.build *args
4
+ builder = new *args
5
+ builder.build
6
+ end
7
+
8
+ attr :argument_names, :injector, :method, :missing_args, :unused_args
9
+
10
+ def initialize method, injector, implicit_block
11
+ @argument_names = method.parameters.map &:last
12
+ @injector = injector
13
+ @method = method
14
+ @opt_arg_name = nil
15
+
16
+ @block_arg = nil
17
+ @implicit_block = implicit_block
18
+ @keyword_args = {}
19
+ @positional_args = []
20
+ @missing_args = []
21
+
22
+ set_unused_args
23
+ end
24
+
25
+ def build
26
+ handle_args!
27
+ check_for_unused_args!
28
+ check_for_missing_args!
29
+ build_invocation
30
+ end
31
+
32
+ def build_invocation
33
+ @block_arg = @implicit_block if @implicit_block
34
+ Invocation.new method.name, @positional_args, @keyword_args, @block_arg
35
+ end
36
+
37
+ def handle_args!
38
+ method.parameters.each do |type, identifier|
39
+ send "handle_#{type}_arg", identifier
40
+ end
41
+ end
42
+
43
+ def handle_req_arg identifier
44
+ arg = injector.fetch identifier do missing_argument! identifier end
45
+ @positional_args << arg
46
+ end
47
+
48
+ def handle_opt_arg identifier
49
+ optional_arg_error! identifier if hit_opt_arg?
50
+ @opt_arg_name = identifier
51
+ arg = injector.fetch identifier do
52
+ build_hash_from_extra_args or return
53
+ end
54
+ @positional_args << arg
55
+ end
56
+
57
+ def handle_keyreq_arg identifier
58
+ arg = injector.fetch identifier do missing_argument! identifier end
59
+ @keyword_args[identifier] = arg
60
+ end
61
+
62
+ def handle_key_arg identifier
63
+ return unless injector.has_key? identifier
64
+ @keyword_args[identifier] = injector[identifier]
65
+ end
66
+
67
+ def handle_block_arg identifier
68
+ if injector.has_key? identifier and @implicit_block
69
+ unused_args << identifier and return
70
+ end
71
+ @block_arg = injector.fetch identifier do
72
+ @implicit_block or missing_argument! identifier
73
+ end
74
+ end
75
+
76
+ def hit_opt_arg?
77
+ @opt_arg_name ? true : false
78
+ end
79
+
80
+ def set_unused_args
81
+ @unused_args = injector.keys.flat_map do |hsh_arg|
82
+ argument_names.include?(hsh_arg) ? [] : [hsh_arg]
83
+ end
84
+ end
85
+
86
+ def build_hash_from_extra_args
87
+ return nil if unused_args.empty?
88
+ hsh = {}
89
+ unused_args.each do |arg| hsh[arg] = injector.fetch arg end
90
+ unused_args.clear
91
+ hsh
92
+ end
93
+
94
+ def check_for_unused_args!
95
+ return if unused_args.empty?
96
+ raise ExtraArgumentsError.new self, unused_args
97
+ end
98
+
99
+ def check_for_missing_args!
100
+ return if missing_args.empty?
101
+ raise MissingArgumentsError.new self, missing_args
102
+ end
103
+
104
+ def optional_arg_error! identifier
105
+ raise OptionalPositionalArgumentError.new(
106
+ method.name,
107
+ @opt_arg_name,
108
+ identifier,
109
+ )
110
+ end
111
+
112
+ def missing_argument! identifier
113
+ missing_args << identifier
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,30 @@
1
+ module Invokr
2
+ module DependencyInjection
3
+ def self.inject args = {}
4
+ klass = args.fetch :klass
5
+ resolver = args.fetch :using
6
+ injector = Injector.new resolver, klass
7
+ injector.inject
8
+ end
9
+
10
+ Injector = Struct.new :resolver, :klass do
11
+ def inject
12
+ invocation = Builder.build initializer, self, nil
13
+ invocation.method = :new
14
+ invocation.invoke! klass
15
+ end
16
+
17
+ def keys
18
+ initializer.parameters.map { |_, identifier| identifier }
19
+ end
20
+
21
+ def fetch arg, &default
22
+ resolver.resolve arg, &default
23
+ end
24
+
25
+ def initializer
26
+ klass.instance_method :initialize
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,63 @@
1
+ module Invokr
2
+ class InputError < ArgumentError
3
+ attr :missing_args
4
+
5
+ def initialize missing_args
6
+ @missing_args = missing_args
7
+ missing_args.map! do |arg| "`#{arg}'" end
8
+ end
9
+
10
+ def message
11
+ @message ||= build_message
12
+ end
13
+
14
+ def build_message
15
+ prefix = "cannot invoke; missing required arguments: "
16
+ prefix << concat_missing_args
17
+ prefix
18
+ end
19
+
20
+ def concat_missing_args
21
+ return missing_args.first if missing_args.size == 1
22
+ last_arg = missing_args.pop
23
+ "#{missing_args.join ', '} and #{last_arg}"
24
+ end
25
+ end
26
+
27
+ class BadArgumentsError < ArgumentError
28
+ attr :builder, :args
29
+
30
+ def initialize builder, args
31
+ @builder = builder
32
+ @args = args
33
+ end
34
+
35
+ def formatted_args
36
+ args.map { |arg| "`#{arg}'" }.join ', '
37
+ end
38
+ end
39
+
40
+ class ExtraArgumentsError < BadArgumentsError
41
+ def message
42
+ %(unused argument(s) #{formatted_args} when invoking method `#{builder.method.name}' on #{builder.method.owner.inspect})
43
+ end
44
+ end
45
+
46
+ class MissingArgumentsError < BadArgumentsError
47
+ def message
48
+ %(missing required argument(s) #{formatted_args} when invoking method `#{builder.method.name}' on #{builder.method.owner.inspect})
49
+ end
50
+ end
51
+
52
+ class OptionalPositionalArgumentError < StandardError
53
+ attr :message
54
+
55
+ def initialize method, arg1, arg2
56
+ @message = <<-MESSAGE
57
+ method `#{method}' has optional positional argument `#{arg2}', after optional argument `#{arg1}'.
58
+
59
+ We cannot use this method because there's no way to supply an explicit value for `#{arg2}' without knowing the default value for `#{arg1}'. It's technically possible to overcome this with S-expression analysis, but a much simpler solution would be to use keyword arguments.
60
+ MESSAGE
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,22 @@
1
+ module Invokr
2
+ Invocation = Struct.new :method, :positional_args, :keyword_args, :block_arg do
3
+ def invoke! obj
4
+ if block_arg?
5
+ obj.public_send method, *args, &block_arg
6
+ else
7
+ obj.public_send method, *args
8
+ end
9
+ end
10
+
11
+ def block_arg?
12
+ block_arg ? true : false
13
+ end
14
+
15
+ def args
16
+ args = positional_args.dup
17
+ args << keyword_args unless keyword_args.empty?
18
+ args
19
+ end
20
+ end
21
+
22
+ end
@@ -0,0 +1,3 @@
1
+ module Invokr
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,64 @@
1
+ class BlockArgsTest < Minitest::Test
2
+ def test_supplying_block_argument_explicitly
3
+ actual = Invokr.invoke(
4
+ method: :block_argument,
5
+ on: TestMethodBank,
6
+ with: { album_block: -> { 'farmhouse' } },
7
+ )
8
+
9
+ assert_equal 'farmhouse', actual
10
+ end
11
+
12
+ def test_supplying_block_argument_implicitly
13
+ actual = Invokr.invoke(
14
+ method: :block_argument,
15
+ on: TestMethodBank,
16
+ with: {},
17
+ block: -> { 'farmhouse' },
18
+ )
19
+
20
+ assert_equal 'farmhouse', actual
21
+ end
22
+
23
+ def test_failing_to_supply_block_argument
24
+ error = assert_raises Invokr::MissingArgumentsError do
25
+ Invokr.invoke(
26
+ method: :block_argument,
27
+ on: TestMethodBank,
28
+ with: {},
29
+ )
30
+ end
31
+
32
+ assert_equal(
33
+ "missing required argument(s) `album_block' when invoking method `block_argument' on #<TestMethodBank:0xdeadbeef>",
34
+ error.message,
35
+ )
36
+ end
37
+
38
+ def test_supplying_block_argument_implicitly_and_explicitly
39
+ error = assert_raises Invokr::ExtraArgumentsError do
40
+ Invokr.invoke(
41
+ method: :block_argument,
42
+ on: TestMethodBank,
43
+ with: { album_block: -> { 'farmhouse' } },
44
+ block: -> { 'farmhouse' },
45
+ )
46
+ end
47
+
48
+ assert_equal(
49
+ "unused argument(s) `album_block' when invoking method `block_argument' on #<TestMethodBank:0xdeadbeef>",
50
+ error.message,
51
+ )
52
+ end
53
+
54
+ def test_implicit_block
55
+ actual = Invokr.invoke(
56
+ method: :just_yields,
57
+ on: TestMethodBank,
58
+ with: {},
59
+ block: -> { 'farmhouse' },
60
+ )
61
+
62
+ assert_equal 'farmhouse', actual
63
+ end
64
+ end
@@ -0,0 +1,41 @@
1
+ require 'invokr/dependency_injection'
2
+
3
+ class DependencyInjectionExampleTest < Minitest::Test
4
+ def setup
5
+ @injector = TestInjector.new(
6
+ :album => 'farmhouse',
7
+ :guitarist => 'trey',
8
+ :drummer => 'fishman',
9
+ )
10
+ end
11
+
12
+ def test_dependency_injection
13
+ @injector.inject TestObject
14
+ end
15
+
16
+ class TestInjector
17
+ def initialize hsh
18
+ @hsh = hsh
19
+ end
20
+
21
+ def inject klass
22
+ Invokr::DependencyInjection.inject(
23
+ :klass => klass,
24
+ :using => self,
25
+ )
26
+ end
27
+
28
+ def resolve val
29
+ @hsh.fetch val
30
+ end
31
+ end
32
+
33
+ class TestObject
34
+ attr :album, :guitarist
35
+
36
+ def initialize album, guitarist
37
+ @album = album
38
+ @guitarist = guitarist
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,12 @@
1
+ class InvokrTest < Minitest::Test
2
+ def test_incorrectly_invoking
3
+ error = assert_raises Invokr::InputError do
4
+ Invokr.invoke
5
+ end
6
+
7
+ assert_equal(
8
+ "cannot invoke; missing required arguments: `method', `on' and `with'",
9
+ error.message,
10
+ )
11
+ end
12
+ end
@@ -0,0 +1,70 @@
1
+ module KeywordArgsTest
2
+ class OptionalKeywordArgsTest < Minitest::Test
3
+ def setup
4
+ skip unless RUBY_VERSION >= '2.0'
5
+ end
6
+
7
+ def test_overriding_optional_keyword_argument
8
+ actual = Invokr.invoke(
9
+ method: :optional_keyword_argument,
10
+ on: TestMethodBank,
11
+ with: { album: 'billy_breathes' },
12
+ )
13
+
14
+ assert_equal 'billy_breathes', actual
15
+ end
16
+
17
+ def test_using_default_optional_keyword_argument
18
+ actual = Invokr.invoke(
19
+ method: :optional_keyword_argument,
20
+ on: TestMethodBank,
21
+ with: {},
22
+ )
23
+
24
+ assert_equal 'pitcher_of_nectar', actual
25
+ end
26
+
27
+ def test_querying_optional_keyword_argument
28
+ method = Invokr.query_method TestMethodBank.method :optional_keyword_argument
29
+
30
+ assert_equal [:album], method.optional_dependencies
31
+ end
32
+ end
33
+
34
+ class RequiredKeywordArgsTest < Minitest::Test
35
+ def setup
36
+ skip unless RUBY_VERSION >= '2.1'
37
+ end
38
+
39
+ def test_supplying_required_keyword_argument
40
+ actual = Invokr.invoke(
41
+ method: :required_keyword_argument,
42
+ on: TestMethodBank,
43
+ with: { album: 'pitcher_of_nectar' },
44
+ )
45
+
46
+ assert_equal 'pitcher_of_nectar', actual
47
+ end
48
+
49
+ def test_failing_to_supply_a_required_keyword_argument
50
+ error = assert_raises Invokr::MissingArgumentsError do
51
+ Invokr.invoke(
52
+ method: :required_keyword_argument,
53
+ on: TestMethodBank,
54
+ with: {},
55
+ )
56
+ end
57
+
58
+ assert_equal(
59
+ "missing required argument(s) `album' when invoking method `required_keyword_argument' on #<TestMethodBank:0xdeadbeef>",
60
+ error.message,
61
+ )
62
+ end
63
+
64
+ def test_querying_required_keyword_argument
65
+ method = Invokr.query_method TestMethodBank.method :required_keyword_argument
66
+
67
+ assert_equal [:album], method.required_dependencies
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,66 @@
1
+ class OptionalArgsTest < Minitest::Test
2
+ def test_overriding_optional_argument
3
+ actual = Invokr.invoke(
4
+ method: :optional_argument,
5
+ on: TestMethodBank,
6
+ with: { album: 'rift' },
7
+ )
8
+
9
+ assert_equal 'rift', actual
10
+ end
11
+
12
+ def test_default_optional_argument
13
+ actual = Invokr.invoke(
14
+ method: :optional_argument,
15
+ on: TestMethodBank,
16
+ with: {},
17
+ )
18
+
19
+ assert_equal 'junta', actual
20
+ end
21
+
22
+ def test_use_extra_args_as_hash_for_optional_argument
23
+ hsh = { guitarist: 'trey' }
24
+
25
+ actual = Invokr.invoke(
26
+ method: :optional_argument,
27
+ on: TestMethodBank,
28
+ with: hsh,
29
+ )
30
+
31
+ assert_equal hsh, actual
32
+ end
33
+
34
+ def test_cant_use_extra_args_as_hash_to_override_optional_argument
35
+ error = assert_raises Invokr::ExtraArgumentsError do
36
+ Invokr.invoke(
37
+ method: :optional_argument,
38
+ on: TestMethodBank,
39
+ with: { album: 'junta', guitarist: 'trey' },
40
+ )
41
+ end
42
+
43
+ assert_equal(
44
+ "unused argument(s) `guitarist' when invoking method `optional_argument' on #<TestMethodBank:0xdeadbeef>",
45
+ error.message,
46
+ )
47
+ end
48
+
49
+ def test_argument_after_optional_argument_raises_error
50
+ error = assert_raises Invokr::OptionalPositionalArgumentError do
51
+ Invokr.invoke(
52
+ method: :double_optional_argument,
53
+ on: TestMethodBank,
54
+ with: {},
55
+ )
56
+ end
57
+
58
+ expected_error_message = <<-MESSAGE
59
+ method `double_optional_argument' has optional positional argument `album2', after optional argument `album1'.
60
+
61
+ We cannot use this method because there's no way to supply an explicit value for `album2' without knowing the default value for `album1'. It's technically possible to overcome this with S-expression analysis, but a much simpler solution would be to use keyword arguments.
62
+ MESSAGE
63
+
64
+ assert_equal expected_error_message, error.message
65
+ end
66
+ end
@@ -0,0 +1,18 @@
1
+ class QueryTest < Minitest::Test
2
+ def setup
3
+ method = TestMethodBank.method :one_required_one_optional_argument
4
+ @method = Invokr.query_method method
5
+ end
6
+
7
+ def test_dependencies
8
+ assert_equal [:album, :guitarist], @method.dependencies
9
+ end
10
+
11
+ def test_optional_dependencies
12
+ assert_equal [:guitarist], @method.optional_dependencies
13
+ end
14
+
15
+ def test_required_dependencies
16
+ assert_equal [:album], @method.required_dependencies
17
+ end
18
+ end
@@ -0,0 +1,41 @@
1
+ class RequiredArgsTest < Minitest::Test
2
+ def test_required_argument
3
+ actual = Invokr.invoke(
4
+ method: :required_argument,
5
+ on: TestMethodBank,
6
+ with: { album: 'junta' },
7
+ )
8
+
9
+ assert_equal 'junta', actual
10
+ end
11
+
12
+ def test_failing_to_supply_required_arguments
13
+ error = assert_raises Invokr::MissingArgumentsError do
14
+ Invokr.invoke(
15
+ method: :multiple_required_arguments,
16
+ on: TestMethodBank,
17
+ with: {},
18
+ )
19
+ end
20
+
21
+ assert_equal(
22
+ "missing required argument(s) `album', `guitarist' when invoking method `multiple_required_arguments' on #<TestMethodBank:0xdeadbeef>",
23
+ error.message,
24
+ )
25
+ end
26
+
27
+ def test_refuses_to_invoke_if_unused_args_are_passed
28
+ error = assert_raises Invokr::ExtraArgumentsError do
29
+ Invokr.invoke(
30
+ method: :required_argument,
31
+ on: TestMethodBank,
32
+ with: { album: 'junta', guitarist: 'trey' },
33
+ )
34
+ end
35
+
36
+ assert_equal(
37
+ "unused argument(s) `guitarist' when invoking method `required_argument' on #<TestMethodBank:0xdeadbeef>",
38
+ error.message,
39
+ )
40
+ end
41
+ end
@@ -0,0 +1,59 @@
1
+ $LOAD_PATH.<< File.expand_path '../../lib', __FILE__
2
+ require 'invokr'
3
+
4
+ require 'minitest'
5
+ require 'minitest/autorun'
6
+ require 'minitest/reporters'
7
+
8
+ Minitest::Reporters.use! Minitest::Reporters::DefaultReporter.new
9
+
10
+ module TestMethodBank
11
+ extend self
12
+
13
+ def required_argument album
14
+ album
15
+ end
16
+
17
+ def optional_argument album = 'junta'
18
+ album
19
+ end
20
+
21
+ def double_optional_argument album1 = 'junta', album2 = 'rift'
22
+ end
23
+
24
+ def block_argument &album_block
25
+ album_block.call
26
+ end
27
+
28
+ def multiple_required_arguments album, guitarist
29
+ [album, guitarist]
30
+ end
31
+
32
+ def one_required_one_optional_argument album, guitarist = 'trey'
33
+ [album, guitarist]
34
+ end
35
+
36
+ def just_yields
37
+ yield
38
+ end
39
+
40
+ def inspect
41
+ "#<#{name}:0xdeadbeef>"
42
+ end
43
+
44
+ if RUBY_VERSION >= '2.0'
45
+ module_eval <<-RB, __FILE__, __LINE__
46
+ def optional_keyword_argument album: 'pitcher_of_nectar'
47
+ album
48
+ end
49
+ RB
50
+ end
51
+
52
+ if RUBY_VERSION >= '2.1'
53
+ module_eval <<-RB, __FILE__, __LINE__
54
+ def required_keyword_argument(album:)
55
+ album
56
+ end
57
+ RB
58
+ end
59
+ end
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: invokr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - ntl
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest-reporters
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Invoke methods with a consistent Hash interface. Useful for metaprogramming.
70
+ email:
71
+ - nathanladd+github@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - Gemfile
78
+ - LICENSE.txt
79
+ - README.md
80
+ - Rakefile
81
+ - bin/rake
82
+ - bin/test_runner
83
+ - invokr.gemspec
84
+ - lib/invokr.rb
85
+ - lib/invokr/builder.rb
86
+ - lib/invokr/dependency_injection.rb
87
+ - lib/invokr/errors.rb
88
+ - lib/invokr/invocation.rb
89
+ - lib/invokr/version.rb
90
+ - test/block_args_test.rb
91
+ - test/dependency_injection_example_test.rb
92
+ - test/invokr_test.rb
93
+ - test/keyword_args_test.rb
94
+ - test/optional_args_test.rb
95
+ - test/query_test.rb
96
+ - test/required_args_test.rb
97
+ - test/test_helper.rb
98
+ homepage: https://github.com/ntl/invokr
99
+ licenses:
100
+ - MIT
101
+ metadata: {}
102
+ post_install_message:
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubyforge_project:
118
+ rubygems_version: 2.3.0
119
+ signing_key:
120
+ specification_version: 4
121
+ summary: Invoke methods with a consistent Hash interface.
122
+ test_files:
123
+ - test/block_args_test.rb
124
+ - test/dependency_injection_example_test.rb
125
+ - test/invokr_test.rb
126
+ - test/keyword_args_test.rb
127
+ - test/optional_args_test.rb
128
+ - test/query_test.rb
129
+ - test/required_args_test.rb
130
+ - test/test_helper.rb