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 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