invokable 0.2.2 → 0.5.0

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: faf0947e19adf5c11f17f5893122d24aa48323807dd364593a38c391e8901c34
4
- data.tar.gz: a482d9ddd514251f362c149eca16774b5b4853159eb52c79653c3f5f8df5b9ac
3
+ metadata.gz: 6dcc085ff857ef0a95578db5f816338c0ade3da6ef4ecb263f86924e4b4e2cfb
4
+ data.tar.gz: 235f058b7b39b8ff91b8d2580281fad56a388fcea46d19ae3a4bc3657c913d1c
5
5
  SHA512:
6
- metadata.gz: 7d2b9ac66665785662b3ab016981a4c747ab32c88e637cb2bfc2a52e34ed353e5f341a5ef178582e7a3cae584576c341aa88907967cd4722acd07c2cb29d001e
7
- data.tar.gz: a89620c1f7127ed27794ef84d45803cbabaf93ceee680edf80a638f4c020551fbde83b3682d8afef70597a692158b157c701d9f3c27b634e5e24476c37f81337
6
+ metadata.gz: bc8294c008fedd2a69337d886515b8fb9c3a2dd6fea8330b15371ffa9b44db0a5ce3c679badede4e2455230db25845ac723b11aa6736f9b62d690b462f7623e9
7
+ data.tar.gz: f6cab459416371d902933ee1ef3fd24f935bcc0ad7b6c9e0a9bd261bf95d8736c763ec416b94b063acf52fbc4a08df04586ac4b6bde8c02aedcb47b8f8074ef9
@@ -0,0 +1,7 @@
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.
data/Gemfile CHANGED
@@ -9,3 +9,7 @@ group :development do
9
9
  gem 'pry'
10
10
  gem 'yard'
11
11
  end
12
+
13
+ group :test do
14
+ gem 'gen-test'
15
+ end
@@ -1,18 +1,27 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- invokable (0.2.2)
4
+ invokable (0.5.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
9
  coderay (1.1.2)
10
+ concurrent-ruby (1.1.5)
10
11
  diff-lcs (1.3)
12
+ faker (1.9.6)
13
+ i18n (>= 0.7)
14
+ gen-test (0.1.1)
15
+ faker (~> 1.9.6)
16
+ regexp-examples (~> 1.5.0)
17
+ i18n (1.8.2)
18
+ concurrent-ruby (~> 1.0)
11
19
  method_source (0.9.2)
12
20
  pry (0.12.2)
13
21
  coderay (~> 1.1.0)
14
22
  method_source (~> 0.9.0)
15
- rake (10.5.0)
23
+ rake (13.0.1)
24
+ regexp-examples (1.5.1)
16
25
  rspec (3.9.0)
17
26
  rspec-core (~> 3.9.0)
18
27
  rspec-expectations (~> 3.9.0)
@@ -33,9 +42,10 @@ PLATFORMS
33
42
 
34
43
  DEPENDENCIES
35
44
  bundler (~> 1.17)
45
+ gen-test
36
46
  invokable!
37
47
  pry
38
- rake (~> 10.0)
48
+ rake (~> 13.0)
39
49
  rspec (~> 3.0)
40
50
  yard
41
51
 
data/README.md CHANGED
@@ -3,66 +3,112 @@
3
3
 
4
4
  # Invokable
5
5
 
6
- Treat any Object, Hashes, Arrays, and Sets as Procs (like Enumerable but for Procs)
6
+ Objects are functions! Treat any Object, Hashes, Arrays, and Sets as Procs (like Enumerable but for Procs)
7
7
 
8
- # Synopsis
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
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
- # Why?
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
+ ```
62
100
 
63
- # API
101
+ And then execute:
64
102
 
65
- ## `to_proc -> Proc`
103
+ > bundle
104
+
105
+ Or install it yourself as:
106
+
107
+ > gem install invokable
108
+
109
+ ## API
110
+
111
+ ### `to_proc => Proc`
66
112
 
67
113
  ```ruby
68
114
  hash = { a: 1, b, 2 }
@@ -76,37 +122,33 @@ returning a proc that passes it's arguments to the object's `call` method. When
76
122
  loaded `Hash#call` is mapped to `Hash#dig`, `Array#call` is mapped to `Array#at`, and `Set#call`
77
123
  is mapped to `Set#include?`.
78
124
 
79
- ## `curry([arity]) -> Proc`
125
+ ### `curry(arity = nil) => Proc`
80
126
 
81
127
  Returns a curried proc. If the `arity` is given, it determines the number of arguments.
82
128
  (see [Proc#curry](https://ruby-doc.org/core-2.7.0/Proc.html#method-i-curry)).
83
129
 
84
- # Installation
130
+ ### `memoize => Proc`
85
131
 
86
- Add this line to your application's Gemfile:
132
+ Returns a memoized proc, that is, a proc that caches it's return values by it's arguments.
87
133
 
88
- ```ruby
89
- gem 'invokable'
90
- ```
91
-
92
- And then execute:
134
+ ### `<<(invokable) => Proc`
93
135
 
94
- > bundle
136
+ Returns a proc that is a composition of this invokable and the given invokable.
95
137
 
96
- Or install it yourself as:
138
+ ### `>>(invokable) => Proc`
97
139
 
98
- > gem install invokable
140
+ Returns a proc that is a composition of this invokable and the given invokable.
99
141
 
100
- # See Also
142
+ ## See Also
101
143
 
144
+ - [Closures and Objects are Equivalent](http://wiki.c2.com/?ClosuresAndObjectsAreEquivalent)
102
145
  - [Clojure](https://clojure.org)
103
146
  - [Arc](http://www.arclanguage.org)
104
147
 
105
- # TODO
148
+ ## TODO
106
149
 
107
- - add support for `memoize` and maybe transducers.
108
150
  - benchmark Invokable#to_proc
109
151
 
110
- # License
152
+ ## License
111
153
 
112
154
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ["Delon Newman"]
9
9
  spec.email = ["contact@delonnewman.name"]
10
10
 
11
- spec.summary = %q{Treat any Object, Hashes, Arrays and Sets as Procs (like Enumerable but for Proc-like objects)}
11
+ spec.summary = %q{Objects are functions! Treat any Object, Hashes, Arrays and Sets as Procs (like Enumerable but for Proc-like objects)}
12
12
  spec.description = spec.summary
13
13
  spec.homepage = "https://github.com/delonnewman/invokable"
14
14
  spec.license = "MIT"
@@ -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,31 +1,16 @@
1
1
  require 'invokable/version'
2
+ require 'invokable/core'
3
+ require 'invokable/compose'
2
4
 
3
- # TODO: Add memoize, transducers?
4
- module Invokable
5
- # If object responds to `call` convert into a Proc forwards it's arguments along to `call`.
6
- #
7
- # @see https://ruby-doc.org/core-2.7.0/Proc.html#method-i-call Proc#call
8
- # @return [Proc]
9
- def to_proc
10
- if respond_to?(:call)
11
- # TODO: Would method(:call) be more performant? We need benchmarks.
12
- Proc.new do |*args|
13
- call(*args)
14
- end
15
- else
16
- raise "Don't know how to convert #{self.inspect} into a Proc"
17
- end
18
- 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
19
10
 
20
- # Return a curried proc. If the optional `arity` argument is given, it determines the number of arguments.
21
- # A curried proc receives some arguments. If a sufficient number of arguments are supplied, it passes the
22
- # supplied arguments to the original proc and returns the result. Otherwise, returns another curried proc
23
- # that takes the rest of arguments.
24
- #
25
- # @see https://ruby-doc.org/core-2.7.0/Proc.html#method-i-curry Proc#curry
26
- # @param arity [Number]
27
- # @return [Proc]
28
- def curry(arity = nil)
29
- to_proc.curry(arity)
11
+ module Invokable
12
+ def self.included(base)
13
+ base.include(Invokable::Core)
14
+ base.include(Invokable::Compose)
30
15
  end
31
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,65 @@
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
+ @initializer ? @initializer.arity : 0
35
+ end
36
+
37
+ # To specify any enclosed state
38
+ def enclose(&block)
39
+ raise 'A block is required' if block.nil?
40
+
41
+ @initializer = block
42
+ define_method :initialize, &block
43
+ end
44
+
45
+ # Handle automatic currying--will accept either the initializer arity or the total arity of the class. If
46
+ # the initializer arity is used return a class instance. If the total arity is used instantiate the class
47
+ # and return the results of the `call` method.
48
+ #
49
+ # @version 0.5.0
50
+ # @see arity
51
+ # @see initializer_arity
52
+ def call(*args)
53
+ if args.length == initializer_arity
54
+ new(*args)
55
+ elsif args.length == arity
56
+ init_args = args.slice(0, initializer_arity)
57
+ call_args = args.slice(initializer_arity, args.length)
58
+ new(*init_args).call(*call_args)
59
+ else
60
+ raise "Wrong number of arguments expected #{initializer_arity} or #{arity}, got: #{args.length}"
61
+ end
62
+ end
63
+ end
64
+ end
65
+ 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.2.2"
2
+ VERSION = "0.5.0"
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.2.2
4
+ version: 0.5.0
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
@@ -52,8 +52,8 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.0'
55
- description: Treat any Object, Hashes, Arrays and Sets as Procs (like Enumerable but
56
- for Proc-like objects)
55
+ description: Objects are functions! Treat any Object, Hashes, Arrays and Sets as Procs
56
+ (like Enumerable but for Proc-like objects)
57
57
  email:
58
58
  - contact@delonnewman.name
59
59
  executables: []
@@ -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,8 +106,8 @@ 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
- summary: Treat any Object, Hashes, Arrays and Sets as Procs (like Enumerable but for
106
- Proc-like objects)
111
+ summary: Objects are functions! Treat any Object, Hashes, Arrays and Sets as Procs
112
+ (like Enumerable but for Proc-like objects)
107
113
  test_files: []