command_class 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.
- checksums.yaml +7 -0
- data/lib/command_class.rb +53 -0
- data/spec/command_class_spec.rb +137 -0
- metadata +46 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: db12173fa0f453ff13d6ca3d6e02ac6a00c51b15aaa307902c4c8fb0a1915a55
|
4
|
+
data.tar.gz: 43265b25a5f2ecd473eacbcfc680b4f5ec52037dade4cb7297236e9fefe51c18
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1af10ca2ecf0be56fe7dd74e8cc5c99545a92bbeb8b478e4121fc93975366096ad546744a2493c34169341daf5c59c519d8faf9d7ddc0474c50f107b1a087a21
|
7
|
+
data.tar.gz: 375eb3cedd42259268a433fa0c9ec8bd0e1d91c5e87a962ea5a5146b7ad47262a7309fc64b52227b3f96250904431a7f451da687bebc395d6fa9ebbae0c5d3ce
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class CommandClass
|
2
|
+
def self.new(dependencies:, inputs:, &blk)
|
3
|
+
cmd_cls = Class.new
|
4
|
+
cmd_cls.const_set('DEFAULT_DEPS', dependencies)
|
5
|
+
|
6
|
+
cmd_cls.class_eval <<~RUBY
|
7
|
+
def initialize(**passed_deps)
|
8
|
+
deps = DEFAULT_DEPS.merge(passed_deps)
|
9
|
+
deps.each { |name, val| instance_variable_set('@' + name.to_s, val) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(#{cmd_call_signature(inputs)})
|
13
|
+
Call.new(#{call_ctor_args(dependencies, inputs)}).()
|
14
|
+
end
|
15
|
+
RUBY
|
16
|
+
|
17
|
+
call_class = Class.new(cmd_cls, &blk)
|
18
|
+
call_class.class_eval <<~RUBY
|
19
|
+
def initialize(#{call_ctor_sig(dependencies, inputs)})
|
20
|
+
#{set_input_attrs(dependencies, inputs)}
|
21
|
+
end
|
22
|
+
RUBY
|
23
|
+
|
24
|
+
cmd_cls.const_set('Call', call_class)
|
25
|
+
cmd_cls
|
26
|
+
end
|
27
|
+
|
28
|
+
class << self
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# TODO: allow for unnamed as well
|
33
|
+
def cmd_call_signature(inputs)
|
34
|
+
inputs.map {|x| "#{x}:" }.join(', ')
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_input_attrs(deps, inputs)
|
38
|
+
all_args(deps, inputs).map {|x| "@#{x} = #{x}" }.join('; ')
|
39
|
+
end
|
40
|
+
|
41
|
+
def call_ctor_sig(deps, inputs)
|
42
|
+
all_args(deps, inputs).join(', ')
|
43
|
+
end
|
44
|
+
|
45
|
+
def call_ctor_args(deps, inputs)
|
46
|
+
[deps.keys.map { |x| "@#{x}" } + inputs].join(', ')
|
47
|
+
end
|
48
|
+
|
49
|
+
def all_args(deps, inputs)
|
50
|
+
deps.keys + inputs
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require_relative"../lib/command_class"
|
3
|
+
|
4
|
+
# Setup classes and collaborators to test
|
5
|
+
#
|
6
|
+
UserRepo = Class.new
|
7
|
+
MyEmailService = Class.new
|
8
|
+
|
9
|
+
module Errors
|
10
|
+
class InvalidName < RuntimeError; end
|
11
|
+
class InvalidEmail < RuntimeError; end
|
12
|
+
class InvalidPassword < RuntimeError; end
|
13
|
+
class EmailAlreadyExists < RuntimeError; end
|
14
|
+
end
|
15
|
+
|
16
|
+
CreateUser = CommandClass.new(
|
17
|
+
dependencies: {user_repo: UserRepo, email_service: MyEmailService},
|
18
|
+
inputs: [:name, :email, :password]
|
19
|
+
) do
|
20
|
+
|
21
|
+
def call
|
22
|
+
validate_input
|
23
|
+
ensure_unique_email
|
24
|
+
insert_user
|
25
|
+
send_confirmation
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def validate_input
|
31
|
+
validate_name
|
32
|
+
validate_email
|
33
|
+
validate_password
|
34
|
+
end
|
35
|
+
|
36
|
+
def ensure_unique_email
|
37
|
+
email_exists = @user_repo.find_by_email(@email)
|
38
|
+
raise Errors::EmailAlreadyExists if email_exists
|
39
|
+
end
|
40
|
+
|
41
|
+
def insert_user
|
42
|
+
@user_repo.insert(name: @name, email: @email, password: @password)
|
43
|
+
end
|
44
|
+
|
45
|
+
def send_confirmation
|
46
|
+
@email_service.send_signup_confirmation(name: @name, email: @email)
|
47
|
+
end
|
48
|
+
|
49
|
+
def validate_name
|
50
|
+
valid = @name.size > 1
|
51
|
+
raise Errors::InvalidName unless valid
|
52
|
+
end
|
53
|
+
|
54
|
+
def validate_email
|
55
|
+
valid = @email =~ /@/
|
56
|
+
raise Errors::InvalidEmail unless valid
|
57
|
+
end
|
58
|
+
|
59
|
+
def validate_password
|
60
|
+
valid = @password.size > 5
|
61
|
+
raise Errors::InvalidPassword unless valid
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
# THE TESTS THEMSELVES
|
67
|
+
#
|
68
|
+
describe CommandClass do
|
69
|
+
|
70
|
+
context "full CreateUser example" do
|
71
|
+
let(:email_svc) { spy('email') }
|
72
|
+
let(:user_repo) { spy('user_repo') }
|
73
|
+
let(:valid_name) { 'John' }
|
74
|
+
let(:valid_email) { 'john@gmail.com' }
|
75
|
+
let(:valid_pw) { 'secret' }
|
76
|
+
|
77
|
+
describe "happy path" do
|
78
|
+
let(:happy_repo) do
|
79
|
+
user_repo.tap do |x|
|
80
|
+
allow(x).to receive(:find_by_email).and_return(nil)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
subject(:create_user) do
|
85
|
+
CreateUser.new(user_repo: happy_repo, email_service: email_svc)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'inserts the user into the db' do
|
89
|
+
create_user.(name: valid_name, email: valid_email, password: valid_pw)
|
90
|
+
expect(user_repo).to have_received(:insert)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'sends the confirmation email' do
|
94
|
+
create_user.(name: valid_name, email: valid_email, password: valid_pw)
|
95
|
+
expect(email_svc).to have_received(:send_signup_confirmation)
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "invalid user input" do
|
101
|
+
subject(:create_user) do
|
102
|
+
CreateUser.new(user_repo: user_repo, email_service: email_svc)
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'errors for a short name' do
|
106
|
+
expect do
|
107
|
+
create_user.(name: 'x', email: valid_email, password: valid_pw)
|
108
|
+
end.to raise_error(Errors::InvalidName)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'errors on an invalid email' do
|
112
|
+
expect do
|
113
|
+
create_user.(name: valid_email, email: 'bad_email', password: valid_pw)
|
114
|
+
end.to raise_error(Errors::InvalidEmail)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "existing email" do
|
119
|
+
let(:repo_with_email) do
|
120
|
+
user_repo.tap do |x|
|
121
|
+
allow(x).to receive(:find_by_email).and_return('user obj')
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
subject(:create_user) do
|
126
|
+
CreateUser.new(user_repo: repo_with_email, email_service: email_svc)
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'errors' do
|
130
|
+
expect do
|
131
|
+
create_user.(name: valid_name, email: valid_email, password: valid_pw)
|
132
|
+
end.to raise_error(Errors::EmailAlreadyExists)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
metadata
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: command_class
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jonah Goldstein
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-09-19 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email:
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/command_class.rb
|
20
|
+
- spec/command_class_spec.rb
|
21
|
+
homepage: https://github.com/jonahx/command_class
|
22
|
+
licenses:
|
23
|
+
- MIT
|
24
|
+
metadata: {}
|
25
|
+
post_install_message:
|
26
|
+
rdoc_options: []
|
27
|
+
require_paths:
|
28
|
+
- lib
|
29
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
requirements: []
|
40
|
+
rubyforge_project:
|
41
|
+
rubygems_version: 2.7.6
|
42
|
+
signing_key:
|
43
|
+
specification_version: 4
|
44
|
+
summary: Create functional command objects without boilerplate
|
45
|
+
test_files:
|
46
|
+
- spec/command_class_spec.rb
|