logicum 0.0.1 → 1.0.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: 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.