invokable 0.3.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f4908866aac724a3af3d142852f6412f3cc93ee7ab8c85f2dd856b08f0039c6c
4
- data.tar.gz: bf5f3d83df9c69f5357eacef3bdc2ecc44384852124f4fc012fa6d3e14716cd5
3
+ metadata.gz: 0a5eab3dc64de36ef9f40927b0d4fa4d68fb0cc1cf867a2f94effa90a0872027
4
+ data.tar.gz: 1fdc343357e9931e7dc5d62e71712159178cd44236ce6d50f7d37a88574d1403
5
5
  SHA512:
6
- metadata.gz: f71bcec50cf1e9d0049259803eae9d5b19e102f78f14fe0854d5e2c238c3b7679552d64bb66608305c497379418f717fa29a0fe1d921c835c3397a58b37d5a47
7
- data.tar.gz: 23827b487b5bb767871585d3bb641f53e8aaa4f28c1e4ef1934e93f200b7665a4a62a34f855767a7bc5424fa472ca62854021e4726b05c0901618ca119a3030c
6
+ metadata.gz: '0760094c175a2e23824bd07ef6830528b09d5ad3f331d1c07dcf35ab2a635d08c9ee3172ff3ef6c7edb0fc83d7d8da36dbd8e76417acfb700f940c6d962c79db'
7
+ data.tar.gz: 278f13b9c4126e75704cffc87f472a2607acfeeabf10c3bd754f22ede54c5c1f2b073aaf399cb9f90f7085ce39bde425da04828648fc98386c1087b0a15e3a10
@@ -0,0 +1,11 @@
1
+ # Change Log
2
+
3
+ ## 0.4.2
4
+
5
+ - `invokable/array` is no longer loaded with `invokable/data`.
6
+ This created a bit of havok in a few places. Including breaking
7
+ puma bootup in Rails 5.2.4.1.
8
+
9
+ ## 0.5.0
10
+
11
+ - Added `Invokable::Command` and `Invokable::Core#arity`
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- invokable (0.3.0)
4
+ invokable (0.5.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -20,7 +20,7 @@ GEM
20
20
  pry (0.12.2)
21
21
  coderay (~> 1.1.0)
22
22
  method_source (~> 0.9.0)
23
- rake (10.5.0)
23
+ rake (13.0.1)
24
24
  regexp-examples (1.5.1)
25
25
  rspec (3.9.0)
26
26
  rspec-core (~> 3.9.0)
@@ -45,7 +45,7 @@ DEPENDENCIES
45
45
  gen-test
46
46
  invokable!
47
47
  pry
48
- rake (~> 10.0)
48
+ rake (~> 13.0)
49
49
  rspec (~> 3.0)
50
50
  yard
51
51
 
data/README.md CHANGED
@@ -8,57 +8,103 @@ Objects are functions! Treat any Object, Hashes, Arrays, and Sets as Procs (like
8
8
  ## Synopsis
9
9
 
10
10
  ```ruby
11
- require 'invokable/hash'
11
+ require 'invokable'
12
+ require 'invokable/hash'
12
13
 
13
- number_names = { 1 => "One", 2 => "Two", 3 => "Three" }
14
- [1, 2, 3, 4].map(&number_names) # => ["One", "Two", "Three", nil]
14
+ number_names = { 1 => "One", 2 => "Two", 3 => "Three" }
15
+ [1, 2, 3, 4].map(&number_names) # => ["One", "Two", "Three", nil]
15
16
  ```
16
17
 
17
18
  ```ruby
18
- require 'invokable/array'
19
+ require 'invokable'
20
+ require 'invokable/array'
19
21
 
20
- alpha = ('a'..'z').to_a
21
- [1, 2, 3, 4].map(&alpha) # => ["b", "c", "d", "e"]
22
+ alpha = ('a'..'z').to_a
23
+ [1, 2, 3, 4].map(&alpha) # => ["b", "c", "d", "e"]
22
24
  ```
23
25
 
24
26
  ```ruby
25
- require 'invokable/set'
27
+ require 'invokable'
28
+ require 'invokable/set'
26
29
 
27
- favorite_numbers = Set[3, Math::PI]
28
- [1, 2, 3, 4].select(&favorite_numbers) # => [3]
30
+ favorite_numbers = Set[3, Math::PI]
31
+ [1, 2, 3, 4].select(&favorite_numbers) # => [3]
29
32
  ```
30
33
 
31
34
  ```ruby
32
- require 'invokable'
35
+ # service objects
36
+ require 'invokable'
33
37
 
34
- # service objects
35
- class GetDataFromSomeService
36
- include Invokable
38
+ class GetDataFromSomeService
39
+ include Invokable
37
40
 
38
- def call(user)
39
- # do the dirt
40
- end
41
+ def call(user)
42
+ # do the dirt
41
43
  end
44
+ end
42
45
 
43
- data_for_user = GetDataFromSomeService.new.memoize # 'memoize' makes a proc that caches results
44
- User.all.map(&data_for_user)
46
+ data_for_user = GetDataFromSomeService.new.memoize # 'memoize' makes a proc that caches results
47
+ User.all.map(&data_for_user)
48
+ ```
49
+ ```ruby
50
+ # command objects that enclose state, can be treated as automatically curried functions.
51
+ require 'invokable'
52
+ require 'invokable/command'
53
+
54
+ class TwitterPoster
55
+ include Invokable::Command
56
+
57
+ enclose do |model|
58
+ @model = model
59
+ end
60
+
61
+ def call(user)
62
+ # do the dirt
63
+ ...
64
+ TwitterStatus.new(user, data)
65
+ end
66
+ end
67
+
68
+ TwitterPoster.call(Model.find(1)) # => #<TwitterPoster ...>
69
+ TwitterPoster.call(Model.find(1), current_user) # => #<TwitterStatus ...>
70
+
71
+ # both the class and it's instances can be used any where Procs are.
72
+
73
+ Model.where(created_at: Date.today).map(&:TwitterPoster) # => [#<TwitterPoster ...>, ...]
45
74
  ```
46
75
 
47
76
  Use as much or a little as you need:
48
77
 
49
78
  ```ruby
50
- require 'invokable' # loads Invokable module
51
- require 'invokable/hash' # loads hash patch
52
- require 'invokable/array' # loads array patch
53
- require 'invokable/set' # loads set patch
54
- require 'invokable/data' # loads all patches
79
+ require 'invokable' # loads Invokable module
80
+ require 'invokable/command' # loads Invokable::Command module
81
+ require 'invokable/hash' # loads hash patch
82
+ require 'invokable/array' # loads array patch
83
+ require 'invokable/set' # loads set patch
84
+ require 'invokable/data' # loads hash and set patches
55
85
  ```
56
86
 
57
87
  ## Why?
58
88
 
59
89
  A function is a mapping of one value to another with the additional constraint that for the one input value you will
60
90
  always get the same output value. So, conceptually, Ruby Hashes, Arrays, and Sets are all functions. Also, there are
61
- many one method objects out there (e.g. ServiceObjects) that are essentially functions. Why not treat them as such?
91
+ many one method objects out there (e.g. Service Objects) that are essentially functions. Why not treat them as such?
92
+
93
+ ## Installation
94
+
95
+ Add this line to your application's Gemfile:
96
+
97
+ ```ruby
98
+ gem 'invokable'
99
+ ```
100
+
101
+ And then execute:
102
+
103
+ > bundle
104
+
105
+ Or install it yourself as:
106
+
107
+ > gem install invokable
62
108
 
63
109
  ## API
64
110
 
@@ -83,26 +129,19 @@ Returns a curried proc. If the `arity` is given, it determines the number of arg
83
129
 
84
130
  ### `memoize => Proc`
85
131
 
86
- Returns a memoized proc, that is, a proc that caches it return values by it's arguments.
132
+ Returns a memoized proc, that is, a proc that caches it's return values by it's arguments.
87
133
 
88
- ## Installation
134
+ ### `<<(invokable) => Proc`
89
135
 
90
- Add this line to your application's Gemfile:
136
+ Returns a proc that is a composition of this invokable and the given invokable.
91
137
 
92
- ```ruby
93
- gem 'invokable'
94
- ```
138
+ ### `>>(invokable) => Proc`
95
139
 
96
- And then execute:
97
-
98
- > bundle
99
-
100
- Or install it yourself as:
101
-
102
- > gem install invokable
140
+ Returns a proc that is a composition of this invokable and the given invokable.
103
141
 
104
142
  ## See Also
105
143
 
144
+ - [Closures and Objects are Equivalent](http://wiki.c2.com/?ClosuresAndObjectsAreEquivalent)
106
145
  - [Clojure](https://clojure.org)
107
146
  - [Arc](http://www.arclanguage.org)
108
147
 
@@ -37,6 +37,6 @@ Gem::Specification.new do |spec|
37
37
  spec.require_paths = ["lib"]
38
38
 
39
39
  spec.add_development_dependency "bundler", "~> 1.17"
40
- spec.add_development_dependency "rake", "~> 10.0"
40
+ spec.add_development_dependency "rake", "~> 13.0"
41
41
  spec.add_development_dependency "rspec", "~> 3.0"
42
42
  end
@@ -1,40 +1,16 @@
1
1
  require 'invokable/version'
2
+ require 'invokable/core'
3
+ require 'invokable/compose'
2
4
 
3
- module Invokable
4
- # If object responds to `call` convert into a Proc forwards it's arguments along to `call`.
5
- #
6
- # @see https://ruby-doc.org/core-2.7.0/Proc.html#method-i-call Proc#call
7
- # @return [Proc]
8
- def to_proc
9
- if respond_to?(:call)
10
- # TODO: Would method(:call) be more performant? We need benchmarks.
11
- Proc.new do |*args|
12
- call(*args)
13
- end
14
- else
15
- raise "Don't know how to convert #{self.inspect} into a Proc"
16
- end
17
- end
18
-
19
- # Return a curried proc. If the optional `arity` argument is given, it determines the number of arguments.
20
- # A curried proc receives some arguments. If a sufficient number of arguments are supplied, it passes the
21
- # supplied arguments to the original proc and returns the result. Otherwise, returns another curried proc
22
- # that takes the rest of arguments.
23
- #
24
- # @see https://ruby-doc.org/core-2.7.0/Proc.html#method-i-curry Proc#curry
25
- # @param arity [Number]
26
- # @return [Proc]
27
- def curry(arity = nil)
28
- to_proc.curry(arity)
29
- end
5
+ # TODO: make use of Gem::Version
6
+ if RUBY_VERSION.split('.').take(2).join('.').to_f < 2.6
7
+ require 'invokable/proc'
8
+ require 'invokable/method'
9
+ end
30
10
 
31
- # Return a memoized proc, that is, a proc that caches it's return values by it's arguments.
32
- #
33
- # @return [Proc]
34
- def memoize
35
- Proc.new do |*args|
36
- @memo ||= {}
37
- @memo[args.hash] ||= call(*args)
38
- end
11
+ module Invokable
12
+ def self.included(base)
13
+ base.include(Invokable::Core)
14
+ base.include(Invokable::Compose)
39
15
  end
40
16
  end
@@ -1,10 +1,10 @@
1
- require_relative '../invokable'
1
+ require_relative 'core'
2
2
 
3
3
  # Extend core Array object by aliasing it's `[]` method as `call`,
4
4
  # and including the `Invokable` module.
5
5
  #
6
6
  # @see https://ruby-doc.org/core-2.7.0/Array.html#method-i-5B-5D Array#[]
7
7
  class Array
8
- include Invokable
8
+ include Invokable::Core
9
9
  alias call []
10
10
  end
@@ -0,0 +1,116 @@
1
+ module Invokable
2
+ # Treat "Command Objects" as curried functions
3
+ #
4
+ # @see https://ruby-doc.org/core-2.7.0/Proc.html#method-i-curry Proc#curry
5
+ #
6
+ # @version 0.5.0
7
+ module Command
8
+ def self.included(klass)
9
+ klass.include(Invokable)
10
+ klass.extend(Invokable::Core)
11
+ klass.extend(Invokable::Compose)
12
+ klass.extend(ClassMethods)
13
+ end
14
+
15
+ module ClassMethods
16
+ # Return the "total" arity of the class (i.e. the arity of the initializer and the arity of the call method)
17
+ #
18
+ # @version 0.5.0
19
+ # @see https://ruby-doc.org/core-2.7.1/Proc.html#method-i-arity Proc#arity
20
+ # @see initializer_arity
21
+ #
22
+ # @return [Integer]
23
+ def arity
24
+ initializer_arity + instance_method(:call).arity
25
+ end
26
+
27
+ # Return the arity of the initializer
28
+ #
29
+ # @version 0.5.0
30
+ # @see arity
31
+ #
32
+ # @return [Integer]
33
+ def initializer_arity
34
+ return @initializer_arity if @initializer_arity
35
+
36
+ @initializer ? @initializer.arity : 0
37
+ end
38
+
39
+ # Handle automatic currying--will accept either the initializer arity or the total arity of the class. If
40
+ # the initializer arity is used return a class instance. If the total arity is used instantiate the class
41
+ # and return the results of the `call` method.
42
+ #
43
+ # @version 0.5.0
44
+ # @see arity
45
+ # @see initializer_arity
46
+ def call(*args)
47
+ if args.length == initializer_arity
48
+ new(*args)
49
+ elsif args.length == arity
50
+ init_args = args.slice(0, initializer_arity)
51
+ call_args = args.slice(initializer_arity, args.length)
52
+ new(*init_args).call(*call_args)
53
+ else
54
+ raise ArgumentError, "wrong number of arguments (given #{args.length}, expected #{initializer_arity} or #{arity})"
55
+ end
56
+ end
57
+
58
+ # Specify any enclosed state with a block or named attributes
59
+ #
60
+ # @example
61
+ # class TwitterPater
62
+ # include Invokable::Command
63
+ #
64
+ # enclose :api_key
65
+ #
66
+ # def call(user)
67
+ # # interact with twitter, return results
68
+ # end
69
+ # end
70
+ #
71
+ # TwitterPater.new(API_KEY).call(User.find(1))
72
+ # TwitterPater.new(API_KEY).api_key == API_KEY # => true
73
+ #
74
+ # class TwitterPater
75
+ # include Invokable::Command
76
+ #
77
+ # enclose do |api_key|
78
+ # @api_key = api_key
79
+ # end
80
+ #
81
+ # def call(user)
82
+ # # interact with twitter, return results
83
+ # end
84
+ # end
85
+ #
86
+ # TwitterPater.new(API_KEY).call(User.find(1))
87
+ # TwitterPater.new(API_KEY).api_key # error 'method' missing
88
+ def enclose(*names, &block)
89
+ return define_initializer_with_block(block) unless block.nil?
90
+
91
+ define_initializer_with_names(names)
92
+ end
93
+
94
+ private
95
+
96
+ def define_initializer_with_block(block)
97
+ @initializer = block
98
+ define_method :initialize, &block
99
+ end
100
+
101
+ def define_initializer_with_names(names)
102
+ @initializer_arity = names.length
103
+
104
+ names.each do |name|
105
+ attr_reader name
106
+ end
107
+
108
+ define_method :initialize do |*args|
109
+ names.each_with_index do |name, i|
110
+ instance_variable_set(:"@#{name}", args[i])
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,25 @@
1
+ module Invokable
2
+ module Compose
3
+ # Return a proc that is the composition of this invokable and the given `invokable`.
4
+ # The returned proc takes a variable number of arguments, calls `invokable` with
5
+ # them then calls this proc with the result.
6
+ #
7
+ # @return [Proc]
8
+ def <<(invokable)
9
+ Proc.new do |*args|
10
+ call(invokable.call(*args))
11
+ end
12
+ end
13
+
14
+ # Return a proc that is the composition of this invokable and the given `invokable`.
15
+ # The returned proc takes a variable number of arguments, calls `invokable` with
16
+ # them then calls this proc with the result.
17
+ #
18
+ # @return [Proc]
19
+ def >>(invokable)
20
+ Proc.new do |*args|
21
+ invokable.call(call(*args))
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,49 @@
1
+ module Invokable
2
+ module Core
3
+ # If object responds to `call` convert into a Proc forwards it's arguments along to `call`.
4
+ #
5
+ # @see https://ruby-doc.org/core-2.7.0/Proc.html#method-i-call Proc#call
6
+ # @return [Proc]
7
+ def to_proc
8
+ if respond_to?(:call)
9
+ # TODO: Would method(:call) be more performant? We need benchmarks.
10
+ Proc.new do |*args|
11
+ call(*args)
12
+ end
13
+ else
14
+ raise "Don't know how to convert #{self.inspect} into a Proc"
15
+ end
16
+ end
17
+
18
+ # Return a curried proc. If the optional `arity` argument is given, it determines the number of arguments.
19
+ # A curried proc receives some arguments. If a sufficient number of arguments are supplied, it passes the
20
+ # supplied arguments to the original proc and returns the result. Otherwise, returns another curried proc
21
+ # that takes the rest of arguments.
22
+ #
23
+ # @see https://ruby-doc.org/core-2.7.0/Proc.html#method-i-curry Proc#curry
24
+ # @param arity [Integer]
25
+ # @return [Proc]
26
+ def curry(arity = nil)
27
+ to_proc.curry(arity)
28
+ end
29
+
30
+ # Return a memoized proc, that is, a proc that caches it's return values by it's arguments.
31
+ #
32
+ # @return [Proc]
33
+ def memoize
34
+ Proc.new do |*args|
35
+ @memo ||= {}
36
+ @memo[args.hash] ||= call(*args)
37
+ end
38
+ end
39
+
40
+ # Return the arity (i.e. the number of arguments) of the `call` method.
41
+ #
42
+ # @version 0.5.0
43
+ # @see https://ruby-doc.org/core-2.7.1/Proc.html#method-i-arity Proc#arity
44
+ # @return [Integer]
45
+ def arity
46
+ method(:call).arity
47
+ end
48
+ end
49
+ end
@@ -1,4 +1,2 @@
1
- require_relative '../invokable'
2
1
  require_relative 'hash'
3
2
  require_relative 'set'
4
- require_relative 'array'
@@ -1,10 +1,12 @@
1
- require_relative '../invokable'
1
+ require_relative 'core'
2
2
 
3
3
  # Extend core Hash object by aliasing it's `dig` method as `call`,
4
4
  # and including the `Invokable` module.
5
5
  #
6
6
  # @see https://ruby-doc.org/core-2.7.0/Hash.html#method-i-dig Hash#dig
7
7
  class Hash
8
- include Invokable
9
- alias call dig
8
+ if RUBY_VERSION.split('.').take(2).join('.').to_f < 2.7
9
+ include Invokable::Core
10
+ alias call dig
11
+ end
10
12
  end
@@ -0,0 +1,5 @@
1
+ require_relative 'compose'
2
+
3
+ class Method
4
+ include Invokable::Compose
5
+ end
@@ -0,0 +1,5 @@
1
+ require_relative 'compose'
2
+
3
+ class Proc
4
+ include Invokable::Compose
5
+ end
@@ -1,11 +1,11 @@
1
1
  require 'set'
2
- require_relative '../invokable'
2
+ require_relative 'core'
3
3
 
4
4
  # Extend stdlib Set object by aliasing it's `include?` method as `call`,
5
5
  # and including the `Invokable` module.
6
6
  #
7
7
  # @see https://ruby-doc.org/stdlib-2.7.0/libdoc/set/rdoc/Set.html#method-i-include-3F Set#include?
8
8
  class Set
9
- include Invokable
9
+ include Invokable::Core
10
10
  alias call include?
11
11
  end
@@ -1,3 +1,3 @@
1
1
  module Invokable
2
- VERSION = "0.3.0"
2
+ VERSION = "0.5.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: invokable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delon Newman
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-10 00:00:00.000000000 Z
11
+ date: 2020-07-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '13.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '13.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -63,6 +63,7 @@ files:
63
63
  - ".github/workflows/ruby.yml"
64
64
  - ".gitignore"
65
65
  - ".rspec"
66
+ - CHANGELOG.md
66
67
  - Gemfile
67
68
  - Gemfile.lock
68
69
  - LICENSE.txt
@@ -71,8 +72,13 @@ files:
71
72
  - invokable.gemspec
72
73
  - lib/invokable.rb
73
74
  - lib/invokable/array.rb
75
+ - lib/invokable/command.rb
76
+ - lib/invokable/compose.rb
77
+ - lib/invokable/core.rb
74
78
  - lib/invokable/data.rb
75
79
  - lib/invokable/hash.rb
80
+ - lib/invokable/method.rb
81
+ - lib/invokable/proc.rb
76
82
  - lib/invokable/set.rb
77
83
  - lib/invokable/version.rb
78
84
  homepage: https://github.com/delonnewman/invokable
@@ -84,7 +90,7 @@ metadata:
84
90
  source_code_uri: https://github.com/delonnewman/invokable
85
91
  changelog_uri: https://github.com/delonnewman/invokable#changelog
86
92
  documentation_uri: https://www.rubydoc.info/gems/invokable
87
- post_install_message:
93
+ post_install_message:
88
94
  rdoc_options: []
89
95
  require_paths:
90
96
  - lib
@@ -100,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
100
106
  version: '0'
101
107
  requirements: []
102
108
  rubygems_version: 3.0.6
103
- signing_key:
109
+ signing_key:
104
110
  specification_version: 4
105
111
  summary: Objects are functions! Treat any Object, Hashes, Arrays and Sets as Procs
106
112
  (like Enumerable but for Proc-like objects)