logicum 0.0.1 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3ab68d2f62115c7415f63a168a7aeea0ec5290b7cde49569461ea436757584b1
4
- data.tar.gz: 1c7fecdd88c64abafa7a13fd7f1ddf820be6efa398cdfdf83b1953d337e6eb8f
3
+ metadata.gz: 98f50316e65e143565d4860ded87ea42f6c15710e9366ad3d461de939090ba9c
4
+ data.tar.gz: ec3ac7c1b95508eabeb1b65a6f893b2b952add91d306ba96701810f90ddeb102
5
5
  SHA512:
6
- metadata.gz: 8e562354af41440cfb932d8a4fcd86dc6f0e670b455a37962fe81b441707e21f1ed64ae7accc4abfe8581ad17c850b0f63b4c76274dc44b5c38a8afa31bc6206
7
- data.tar.gz: 101c01166953568e1e8cce92e470d8c98ef65dc07796a6da4fe7204a9c921ea8020868eea8b881dcdae11cda41b0de857c190e953ff49c548293b2ba53894eae
6
+ metadata.gz: e193993fec01cbf098e429750d2fe0a102c7742a547cb674520986e34abc5c686781d009dbd832ef105b745cbc2e03db614fe3e829595f306b09373c73af6a93
7
+ data.tar.gz: ad952edbb3aa6cd4f17ea7f5e8057890e7e22299b53b32866700be6c3f5c793c6ab9c31418511ad4009e9db4cae0ed0d864384616d5dc0702914bb51a58072b3
data/.gitignore CHANGED
@@ -6,3 +6,4 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ Gemfile.lock
data/README.md CHANGED
@@ -1,8 +1,140 @@
1
1
  # Logicum
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/logicum`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ A simple, consistent interface for executing a unit of business logic.
4
+
5
+
6
+ ## Usage
7
+
8
+ In a nutshell:
9
+
10
+ ```ruby
11
+ class DoSomething
12
+ # Turn your object into an interactor.
13
+ include Logicum::Interactor
14
+
15
+ # Declare any values available on the result of call().
16
+ # You must set these instance variables in your call() method.
17
+ provides :foo, :bar
18
+
19
+ # Encapsulate your logic in a call() method.
20
+ #
21
+ # The call() method will not raise an error if your logic raises an error.
22
+ # Instead the result will be a failure and the error message will be available.
23
+ #
24
+ # Returns a result object which responds to :success?, :failure?, and :error.
25
+ def call(params:)
26
+ # do stuff
27
+
28
+ # Set the variables to provide on the result.
29
+ @foo = params[:foo] + 3
30
+ @bar = 153
31
+ end
32
+ end
33
+
34
+ # And you use it like this:
35
+
36
+ result = DoSomething.new.call params: {foo: 42}
37
+ result.success? # true
38
+ result.foo # 45
39
+ result.bar # 153
40
+ ```
41
+
42
+ If you don't need to pass arguments into your initializer, you can send `:call` to the class instead:
43
+
44
+ ```ruby
45
+ result = DoSomething.call params: {foo: 42}
46
+ ```
47
+
48
+ The result is successful unless an exception is raised. You can also explicitly make the result a failure using the `fail!` method, which takes an optional string message.
49
+
50
+ ```ruby
51
+ class DoSomething
52
+ include Logicum::Interactor
53
+
54
+ def call(foo:)
55
+ fail! 'This went wrong'
56
+ end
57
+ end
58
+
59
+ result = DoSomething.call 153
60
+ result.failure? # true
61
+ resut.error # 'This went wrong'
62
+ ```
63
+
64
+ ## Purpose
65
+
66
+ The motivation was to move all business logic out of Rails controllers.
67
+
68
+ Instead of this:
69
+
70
+ ```ruby
71
+ class UsersController < ApplicationController
72
+
73
+ def create
74
+ @user = User.new user_params
75
+
76
+ if @user.save
77
+ redirect_to @user
78
+ else
79
+ render :edit
80
+ end
81
+ end
82
+
83
+ end
84
+ ```
85
+
86
+ You can write this:
87
+
88
+ ```ruby
89
+ class AddUser
90
+ include Logicum::Interactor
91
+
92
+ provides :user
93
+
94
+ def call(params)
95
+ @user = User.new params
96
+ @user.save!
97
+ end
98
+ end
99
+
100
+
101
+ class UsersController < ApplicationController
102
+
103
+ def create
104
+ result = AddUser.call user_params
105
+
106
+ if result.success?
107
+ redirect_to result.user
108
+ else
109
+ @user = result.user
110
+ render :edit
111
+ end
112
+ end
113
+
114
+ end
115
+ ```
116
+
117
+ This is more code, so why bother?
118
+
119
+ The controller no longer has any business logic in it. It simply mediates between HTTP and your domain. We have separated concerns, reduced coupling, and increased cohesion.
120
+
121
+ It's a consistent interface.
122
+
123
+ If you situate all your business operations in a directory, e.g. `app/interactors/` or `app/services/`, you can see at a glance everything your application does.
124
+
125
+ The more complicated your logic gets, the more appealing this approach is. The example above is simple and therefore not especially compelling :) However as your application grows and you add business logic – e.g. sending emails, updating analytics, triggering background jobs – you can do it without cluttering up your controllers with details they should not know about.
126
+
127
+
128
+ ## Inspiration
129
+
130
+ Although the command object / service object pattern has been around for ages I have never felt it was worthwhile for my applications. However recently these three libraries persuaded me otherwise.
131
+
132
+ - GoCardless's [Coach](https://github.com/gocardless/coach)
133
+ - CollectiveIdea's [Interactor](https://github.com/collectiveidea/interactor)
134
+ - Hanami's [Interactor](https://github.com/hanami/utils/blob/a74304af5bb69f6a561aad2718943388fac30782/lib/hanami/interactor.rb)
135
+
136
+ I wanted something even lighter weight, providing just enough structure for the benefits to materialise, so I wrote my own.
4
137
 
5
- TODO: Delete this and the text above, and describe your gem
6
138
 
7
139
  ## Installation
8
140
 
@@ -20,19 +152,6 @@ Or install it yourself as:
20
152
 
21
153
  $ gem install logicum
22
154
 
23
- ## Usage
24
-
25
- TODO: Write usage instructions here
26
-
27
- ## Development
28
-
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
-
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
-
33
- ## Contributing
34
-
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/airblade/logicum.
36
155
 
37
156
  ## License
38
157
 
@@ -1,6 +1,5 @@
1
- require "logicum/version"
1
+ require 'logicum/interactor'
2
+ require 'logicum/version'
2
3
 
3
4
  module Logicum
4
- class Error < StandardError; end
5
- # Your code goes here...
6
5
  end
@@ -0,0 +1,42 @@
1
+ # https://github.com/hanami/utils/blob/master/lib/hanami/utils/class_attribute.rb
2
+
3
+ require 'set'
4
+
5
+ module Logicum
6
+ module ClassAttribute
7
+
8
+ def self.included(base)
9
+ base.extend ClassMethods
10
+ end
11
+
12
+
13
+ module ClassMethods
14
+ def class_attribute(*attributes)
15
+ singleton_class.class_eval do
16
+ attr_accessor *attributes
17
+ end
18
+
19
+ class_attributes.merge attributes
20
+ end
21
+
22
+ protected
23
+
24
+ def inherited(subclass)
25
+ class_attributes.each do |attribute|
26
+ value = send(attribute).dup
27
+ subclass.class_attribute attribute
28
+ subclass.send "#{attribute}=", value
29
+ end
30
+
31
+ super
32
+ end
33
+
34
+ private
35
+
36
+ def class_attributes
37
+ @class_attributes ||= Set.new
38
+ end
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,12 @@
1
+ module Logicum
2
+
3
+ Error = Class.new StandardError
4
+
5
+ # The business object does not implement a call() instance method.
6
+ MissingCallError = Class.new Error
7
+
8
+ # The business object declares that it provides a value, but it was
9
+ # not set in the call() instance method.
10
+ ProvisionError = Class.new Error
11
+
12
+ end
@@ -0,0 +1,75 @@
1
+ require 'logicum/class_attribute'
2
+ require 'logicum/result'
3
+ require 'logicum/errors'
4
+
5
+ module Logicum
6
+ module Interactor
7
+
8
+ def self.included(base)
9
+ base.extend ClassMethods
10
+ base.prepend CallInterface
11
+ end
12
+
13
+
14
+ module ClassMethods
15
+ def self.extended(base)
16
+ base.class_eval do
17
+ include ClassAttribute
18
+ class_attribute :provisions
19
+ self.provisions = []
20
+ end
21
+ end
22
+
23
+ def provides(*instance_variable_names)
24
+ provisions.concat instance_variable_names
25
+ end
26
+
27
+ # Shortcut for caller if nothing needed in intializer.
28
+ # For example:
29
+ #
30
+ # AddUser.call foo: 'bar'
31
+ #
32
+ # is equivalent to:
33
+ #
34
+ # AddUser.new.call foo: 'bar'
35
+ def call(*args, &block)
36
+ new.call *args, &block
37
+ end
38
+ end
39
+
40
+
41
+ module CallInterface
42
+ def call(*)
43
+ raise MissingCallError unless defined? super
44
+
45
+ @__result__ = Result.new
46
+
47
+ begin
48
+ super
49
+ rescue StandardError => e
50
+ @__result__.fail! e.message
51
+ end
52
+
53
+ self.class.provisions.each do |attr|
54
+ ivar_name = "@#{attr}"
55
+ if instance_variable_defined? ivar_name
56
+ val = instance_variable_get ivar_name
57
+ @__result__.define_singleton_method(attr) { val }
58
+ else
59
+ # Calling code must ensure instance variables to provide are
60
+ # set before any code which could raise an exception.
61
+ raise ProvisionError, "#{ivar_name} was not set in call() method"
62
+ end
63
+ end
64
+
65
+ @__result__
66
+ end
67
+ end
68
+
69
+
70
+ def fail!(message = '')
71
+ @__result__.fail! message
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,30 @@
1
+ module Logicum
2
+ module Interactor
3
+
4
+ class Result
5
+ def initialize
6
+ @success = true
7
+ @error = ''
8
+ end
9
+
10
+ def success?
11
+ @success
12
+ end
13
+
14
+ def failure?
15
+ !success?
16
+ end
17
+
18
+ def fail!(message = '')
19
+ @success = false
20
+ @error = message
21
+ end
22
+
23
+ def error
24
+ @error.dup
25
+ end
26
+ end
27
+
28
+ end
29
+ end
30
+
@@ -1,3 +1,3 @@
1
1
  module Logicum
2
- VERSION = "0.0.1"
2
+ VERSION = '1.0.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logicum
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Stewart
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-31 00:00:00.000000000 Z
11
+ date: 2019-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -67,6 +67,10 @@ files:
67
67
  - bin/console
68
68
  - bin/setup
69
69
  - lib/logicum.rb
70
+ - lib/logicum/class_attribute.rb
71
+ - lib/logicum/errors.rb
72
+ - lib/logicum/interactor.rb
73
+ - lib/logicum/result.rb
70
74
  - lib/logicum/version.rb
71
75
  - logicum.gemspec
72
76
  homepage: https://github.com/airblade/logicum
@@ -88,7 +92,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
88
92
  - !ruby/object:Gem::Version
89
93
  version: '0'
90
94
  requirements: []
91
- rubygems_version: 3.0.1
95
+ rubyforge_project:
96
+ rubygems_version: 2.7.3
92
97
  signing_key:
93
98
  specification_version: 4
94
99
  summary: Simplifies writing a unit of business logic.