obvious 0.0.8 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9a0c1346518213e685185d08916b2c573697ca5d6be5763f8cfe245febeac4f0
4
+ data.tar.gz: 2125df9f05284d2e34b16edb85ee14f73d1d1f2a296df90c7a2b4e2a795ab81f
5
+ SHA512:
6
+ metadata.gz: 1f8684fe2bc370eb3d2cec4933e3281a00b0633a5829cdee580b36632d330e6b71ba605612bd1ef5f5cfa6884ed66369a50117f68d3b1a9a9ec73a0c3e5bdfda
7
+ data.tar.gz: f393f71805593ff5b748477ebe9486148c356fdb73b1fb09f108453611ceabf4cce982768914c10de719012b0782659bf57ab4e91357871173551d60fb4503ff
@@ -0,0 +1,19 @@
1
+ name: obvious
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ unit-tests:
7
+ strategy:
8
+ matrix:
9
+ ruby: [ 2.7, '3.0', 3.1 ]
10
+ name: Ruby ${{ matrix.ruby }} unit tests
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v2
14
+ - uses: ruby/setup-ruby@v1
15
+ with:
16
+ ruby-version: ${{ matrix.ruby }}
17
+ bundler-cache: true
18
+ - run: |
19
+ ruby -Ilib:test test/*_test.rb
data/.gitignore CHANGED
@@ -4,7 +4,6 @@
4
4
  .bundle
5
5
  .config
6
6
  .yardoc
7
- Gemfile.lock
8
7
  InstalledFiles
9
8
  _yardoc
10
9
  coverage
data/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [0.2.0]
6
+
7
+ ### Added
8
+
9
+ ### Changed
10
+
11
+ - Updated README to be current
12
+ - Removed generators from Obvious
13
+ - Refactored Obvious::Obj#define to be simpler
14
+ - Replaced RSPec with Minitest for testing
15
+
16
+ ### Fixed
17
+
18
+ - Fixed namespace issues with Contract errors
19
+ - Updated project URL on Rubygems listing
data/Gemfile.lock ADDED
@@ -0,0 +1,19 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ obvious (0.2.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+
10
+ PLATFORMS
11
+ ruby
12
+ x86_64-darwin-20
13
+ x86_64-linux
14
+
15
+ DEPENDENCIES
16
+ obvious!
17
+
18
+ BUNDLED WITH
19
+ 2.3.4
data/README.md CHANGED
@@ -1,10 +1,9 @@
1
1
  # Obvious
2
2
 
3
- Obvious is an architecture framework. The goal is to provide architectural structure for a highly testable system that is
4
- obvious to understand and where both the front end UI and back end infrastructure are treated as implementation details
5
- independent of the app logic itself.
6
-
7
- You can get a full explanation of Obvious at http://obvious.retromocha.com
3
+ Obvious is an architecture framework. The goal is to provide architectural
4
+ structure for a highly testable system that is obvious to understand and where
5
+ both the front end UI and back end infrastructure are treated as implementation
6
+ details independent of the app logic itself.
8
7
 
9
8
  ## Installation
10
9
 
@@ -15,31 +14,3 @@ Add this line to your application's Gemfile:
15
14
  And then execute:
16
15
 
17
16
  $ bundle
18
-
19
- Or install it yourself as:
20
-
21
- $ gem install obvious
22
-
23
- ## Usage
24
-
25
- Obvious is designed to be used in two ways - as a gem you require in your Obvious projects and also as an Obvious project
26
- generation tool.
27
-
28
- The project generation tool is run by executing...
29
-
30
- $ obvious generate
31
-
32
- in a directory containing a decriptors directory. You can read more about descriptors on the Obvious page or see an example
33
- in the Obvious Status example app: https://github.com/RetroMocha/obvious_status.
34
-
35
- Currently the footprint of the Obvious library is quite small. The most important things defined so far are the Contract class
36
- and the Hash.has_shape? method. The rest of what makes an Obvious app interesting is the structure itself, not the libraries Obvious
37
- provides.
38
-
39
- ## Contributing
40
-
41
- 1. Fork it
42
- 2. Create your feature branch (`git checkout -b my-new-feature`)
43
- 3. Commit your changes (`git commit -am 'Add some feature'`)
44
- 4. Push to the branch (`git push origin my-new-feature`)
45
- 5. Create new Pull Request
data/Rakefile CHANGED
@@ -1 +1,9 @@
1
1
  require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+ task :default => :test
@@ -1,166 +1,179 @@
1
- class Contract
2
- @@disable_override = false
1
+ module Obvious
3
2
 
4
- # Provides a default empty array for method_added
5
- # Overriden by self.contracts
6
- def self.contract_list
7
- []
8
- end
9
-
10
- # Public: Sets the contracts
11
- #
12
- # contracts - a list of contracts, as String or as Symbols.
13
- #
14
- # Examples
15
- #
16
- # class FooJackContract < Contract
17
- # contracts :save, :get, :list
18
- #
19
- # end
20
- #
21
- # Returns Nothing.
22
- def self.contracts *contracts
23
- singleton_class.send :define_method, :contract_list do
24
- contracts
25
- end
26
- end
3
+ class Contract
4
+ @@disable_override = false
27
5
 
28
- # Public: Defines a contract for a method
29
- #
30
- # method - a symbol representing the method name
31
- # contract - a hash with the keys :input and :output holding the respective shapes.
32
- #
33
- # Examples
34
- #
35
- # class FooJackContract < Contract
36
- #
37
- # contract_for :save, {
38
- # :input => Foo.shape,
39
- # :output => Foo.shape,
40
- # }
41
- #
42
- # end
43
- #
44
- def self.contract_for method, contract
45
- method_alias = "#{method}_alias".to_sym
46
- method_contract = "#{method}_contract".to_sym
47
-
48
- define_method method_contract do |*args|
49
- input = args[0]
50
- call_method method_alias, input, contract[:input], contract[:output]
6
+ # Provides a default empty array for method_added
7
+ # Overriden by self.contracts
8
+ def self.contract_list
9
+ []
51
10
  end
52
11
 
53
- contracts( *contract_list, method )
54
- end
55
-
56
- # This method will move methods defined in @contracts into new methods.
57
- # Each entry in @contracts will cause the method with the same name to
58
- # become method_name_alias and for the original method to point to
59
- # method_name_contract.
60
- def self.method_added name
61
- unless @@disable_override
62
- self.contract_list.each do |method|
63
- if name == method.to_sym
64
- method_alias = "#{method}_alias".to_sym
65
- method_contract = "#{method}_contract".to_sym
66
-
67
- @@disable_override = true # to stop the new build method
68
- self.send :alias_method, method_alias, name
69
- self.send :remove_method, name
70
- self.send :alias_method, name, method_contract
71
-
72
- @@disable_override = false
73
- else
74
- # puts self.inspect
75
- # puts "defining other method #{name}"
76
- end
12
+ # Public: Sets the contracts
13
+ #
14
+ # contracts - a list of contracts, as String or as Symbols.
15
+ #
16
+ # Examples
17
+ #
18
+ # class FooJackContract < Contract
19
+ # contracts :save, :get, :list
20
+ #
21
+ # end
22
+ #
23
+ # Returns Nothing.
24
+ def self.contracts *contracts
25
+ singleton_class.send :define_method, :contract_list do
26
+ contracts
77
27
  end
78
28
  end
79
- end
80
29
 
81
- # This method is used as a shorthand to mak the contract method calling pattern more DRY
82
- # It starts by checking if you are sending in input and if so will check the input shape for
83
- # errors. If no errors are found it calls the method via the passed in symbol(method).
84
- #
85
- # Output checking is more complicated because of the types of output we check for. Nil is
86
- # never valid output. If we pass in the output shape of true, that means we are looking for
87
- # result to be the object True. If the output shape is an array, that is actually a shorthand
88
- # for telling our output check to look at the output as an array and compare it to the shape
89
- # stored in output_shape[0]. If we pass in the symbol :true_false it means we are looking for
90
- # the result to be either true or false. The default case will just check if result has the shape
91
- # of the output_shape.
92
- def call_method method, input, input_shape, output_shape
93
- if input != nil && input_shape != nil
94
- has_shape, error_field = input.has_shape? input_shape, true
95
- unless has_shape
96
- raise ContractInputError, "incorrect input data format field #{error_field}"
30
+ # Public: Defines a contract for a method
31
+ #
32
+ # method - a symbol representing the method name
33
+ # contract - a hash with the keys :input and :output holding the respective shapes.
34
+ #
35
+ # Examples
36
+ #
37
+ # class FooJackContract < Contract
38
+ #
39
+ # contract_for :save, {
40
+ # :input => Foo.shape,
41
+ # :output => Foo.shape,
42
+ # }
43
+ #
44
+ # end
45
+ #
46
+ def self.contract_for method, contract
47
+ method_alias = "#{method}_alias".to_sym
48
+ method_contract = "#{method}_contract".to_sym
49
+
50
+ define_method method_contract do |*args|
51
+ input = args[0]
52
+ call_method method_alias, input, contract[:input], contract[:output]
97
53
  end
98
54
 
99
- result = self.send method, input
100
- else
101
- result = self.send method
55
+ contracts( *contract_list, method )
102
56
  end
103
57
 
104
- # check output
105
- # output should never be nil
106
- if result == nil
107
- raise ContractOutputError, 'incorrect output data format'
58
+ # This method will move methods defined in @contracts into new methods.
59
+ # Each entry in @contracts will cause the method with the same name to
60
+ # become method_name_alias and for the original method to point to
61
+ # method_name_contract.
62
+ def self.method_added name
63
+ unless @@disable_override
64
+ self.contract_list.each do |method|
65
+ if name == method.to_sym
66
+ method_alias = "#{method}_alias".to_sym
67
+ method_contract = "#{method}_contract".to_sym
68
+
69
+ @@disable_override = true # to stop the new build method
70
+ self.send :alias_method, method_alias, name
71
+ self.send :remove_method, name
72
+ self.send :alias_method, name, method_contract
73
+
74
+ @@disable_override = false
75
+ else
76
+ # puts self.inspect
77
+ # puts "defining other method #{name}"
78
+ end
79
+ end
80
+ end
108
81
  end
109
82
 
110
- if result === {}
111
- raise DataNotFoundError, 'data was not found'
112
- end
83
+ # This method is used as a shorthand to mak the contract method calling pattern more DRY
84
+ # It starts by checking if you are sending in input and if so will check the input shape for
85
+ # errors. If no errors are found it calls the method via the passed in symbol(method).
86
+ #
87
+ # Output checking is more complicated because of the types of output we check for. Nil is
88
+ # never valid output. If we pass in the output shape of true, that means we are looking for
89
+ # result to be the object True. If the output shape is an array, that is actually a shorthand
90
+ # for telling our output check to look at the output as an array and compare it to the shape
91
+ # stored in output_shape[0]. If we pass in the symbol :true_false it means we are looking for
92
+ # the result to be either true or false. The default case will just check if result has the shape
93
+ # of the output_shape.
94
+ def call_method method, input, input_shape, output_shape
95
+ if input != nil && input_shape != nil
96
+ has_shape, error_field = input.has_shape? input_shape, true
97
+ unless has_shape
98
+ raise Obvious::ContractInputError, "incorrect input data format field #{error_field}"
99
+ end
113
100
 
114
- # we are looking for result to be a True object
115
- if output_shape === true
116
- if output_shape == result
117
- return result
101
+ result = self.send method, input
118
102
  else
119
- raise ContractOutputError, 'incorrect output data format'
103
+ result = self.send method
104
+ end
105
+
106
+ # check output
107
+ # output should never be nil
108
+ if result == nil
109
+ raise Obvious::ContractOutputError, 'incorrect output data format'
110
+ end
111
+
112
+ if result === {}
113
+ raise Obvious::DataNotFoundError, 'data was not found'
114
+ end
115
+
116
+ # we are looking for result to be a True object
117
+ if output_shape === true
118
+ if output_shape == result
119
+ return result
120
+ else
121
+ raise Obvious::ContractOutputError, 'incorrect output data format'
122
+ end
120
123
  end
121
- end
122
124
 
123
- # we want to check the shape of each item in the result array
124
- if output_shape.class == Array
125
- if result.class == Array
126
- inner_shape = output_shape[0]
127
- result.each do |item|
128
- has_shape, error_field = item.has_shape? inner_shape, true
129
- unless has_shape
130
- raise ContractOutputError, "incorrect output data format field #{error_field}"
125
+ # we want to check the shape of each item in the result array
126
+ if output_shape.class == Array
127
+ if result.class == Array
128
+ inner_shape = output_shape[0]
129
+ result.each do |item|
130
+ has_shape, error_field = item.has_shape? inner_shape, true
131
+ unless has_shape
132
+ raise Obvious::ContractOutputError, "incorrect output data format field #{error_field}"
133
+ end
131
134
  end
135
+
136
+ return result
137
+ end
138
+ raise Obvious::ContractOutputError, 'incorrect output data format'
139
+ end
140
+
141
+ # we want result to be true or false
142
+ if output_shape == :true_false
143
+ unless result == true || result == false
144
+ raise Obvious::ContractOutputError, 'incorrect output data format'
132
145
  end
133
146
 
134
147
  return result
135
148
  end
136
- raise ContractOutputError, 'incorrect output data format'
137
- end
138
149
 
139
- # we want result to be true or false
140
- if output_shape == :true_false
141
- unless result == true || result == false
142
- raise ContractOutputError, 'incorrect output data format'
150
+ # we want result to be output_shape's shape
151
+ has_shape, error_field = result.has_shape? output_shape, true
152
+ unless has_shape
153
+ raise Obvious::ContractOutputError, "incorrect output data format field #{error_field}"
143
154
  end
144
155
 
145
- return result
156
+ result
146
157
  end
147
158
 
148
- # we want result to be output_shape's shape
149
- has_shape, error_field = result.has_shape? output_shape, true
150
- unless has_shape
151
- raise ContractOutputError, "incorrect output data format field #{error_field}"
152
- end
159
+ end
160
+
161
+
162
+ class ContractInputError < StandardError
163
+ end
153
164
 
154
- result
165
+ class ContractOutputError < StandardError
155
166
  end
156
167
 
168
+ class DataNotFoundError < StandardError
169
+ end
157
170
  end
158
171
 
159
172
  # via https://github.com/citizen428/shenanigans/blob/master/lib/shenanigans/hash/has_shape_pred.rb
160
173
  class Hash
161
174
  # Checks if a hash has a certain structure.
162
175
  # h = { k1: 1, k2: "1" }
163
- # h.has_shape?(k1: Fixnum, k2: String)
176
+ # h.has_shape?(k1: Integer, k2: String)
164
177
  # #=> true
165
178
  # h.has_shape?(k1: Class, k2: String)
166
179
  # #=> false
@@ -174,28 +187,28 @@ class Hash
174
187
  if return_field
175
188
  return r, f
176
189
  else
177
- return r
190
+ return r
178
191
  end
179
192
  }
180
-
193
+
181
194
  # I added an empty check
182
195
  if self.empty?
183
196
  return return_value.call shape.empty?, nil
184
- end
185
-
197
+ end
198
+
186
199
  self.each do |k, v|
187
200
  return return_value.call false, k if shape[k] == nil
188
- end
201
+ end
189
202
 
190
203
  shape.each do |k, v|
191
204
  # hash_value
192
205
  hv = self[k]
193
- return return_value.call false, k unless self.has_key? k
206
+ return return_value.call false, k unless self.has_key? k
194
207
 
195
208
  next if hv === nil
196
209
 
197
- if Hash === hv
198
- return hv.has_shape?(v, return_field)
210
+ if Hash === hv
211
+ return hv.has_shape?(v, return_field)
199
212
  else
200
213
  return return_value.call false, k unless v === hv
201
214
  end
@@ -204,14 +217,11 @@ class Hash
204
217
  return_value.call true, nil
205
218
  end
206
219
 
207
- end
208
-
209
- class ContractInputError < StandardError
210
- end
211
-
212
- class ContractOutputError < StandardError
213
- end
220
+ def nil_fields? list
221
+ list.each do |field|
222
+ return true, field unless self[field]
223
+ end
214
224
 
215
- class DataNotFoundError < StandardError
225
+ return false, nil
226
+ end
216
227
  end
217
-
@@ -0,0 +1,36 @@
1
+ module Obvious
2
+ module Obj
3
+ class << self
4
+ def included(base)
5
+ base.extend ClassMethods
6
+ end
7
+ end
8
+
9
+ module ClassMethods
10
+
11
+ def define method, input = {}, &block
12
+ define_method(method) do |method_input = {}|
13
+ block_input = {}
14
+ method_input.each do |k,v|
15
+ if input[k].nil?
16
+ raise ArgumentError.new "invalid input field #{k}"
17
+ end
18
+
19
+ unless v.is_a? input[k]
20
+ raise ArgumentError.new "invalid type for #{k} expected #{input[k]}"
21
+ end
22
+ end
23
+
24
+ input.each do |k,v|
25
+ if method_input[k].nil?
26
+ raise ArgumentError.new "missing input field #{k}"
27
+ end
28
+ end
29
+
30
+ self.instance_exec method_input, &block
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -1,3 +1,3 @@
1
1
  module Obvious
2
- VERSION = "0.0.8"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/obvious.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  require 'obvious/version'
2
2
  require 'obvious/contract'
3
3
  require 'obvious/entity'
4
- require_relative 'generators/application_generator'
4
+ require 'obvious/obj'
data/obvious.gemspec CHANGED
@@ -6,16 +6,14 @@ require 'obvious/version'
6
6
  Gem::Specification.new do |gem|
7
7
  gem.name = "obvious"
8
8
  gem.version = Obvious::VERSION
9
- gem.authors = ["Brian Knapp"]
10
- gem.email = ["brianknapp@gmail.com"]
9
+ gem.authors = ["Brian Knapp", "Shawn Baden"]
10
+ gem.email = ["brianknapp@gmail.com", "shawnbaden@hotmail.com"]
11
11
  gem.description = "A set of tools to build apps using the Obvious Architecture"
12
- gem.summary = "Isn't it Obvious?"
13
- gem.homepage = "http://obvious.retromocha.com/"
14
-
12
+ gem.summary = "Clean Architecture framework"
13
+ gem.homepage = "https://github.com/RetroMocha/obvious"
14
+ gem.license = "MIT"
15
15
  gem.files = `git ls-files`.split($/)
16
16
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
17
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
18
  gem.require_paths = ["lib"]
19
-
20
- gem.add_development_dependency "rspec"
21
19
  end
@@ -0,0 +1,62 @@
1
+ require 'minitest/autorun'
2
+ require_relative '../lib/obvious/contract'
3
+
4
+ class TestContract < Obvious::Contract
5
+ contract_for :test, {
6
+ input: { id: Integer },
7
+ output: { id: Integer, value: String }
8
+ }
9
+
10
+ def test input
11
+ { id: 1, value: 'this is a test' }
12
+ end
13
+ end
14
+
15
+ class ContractTest < Minitest::Test
16
+ def test_valid_input
17
+ result = TestContract.new.test(id: 1)
18
+ assert_equal({id: 1, value: 'this is a test'}, result)
19
+ end
20
+
21
+ def test_invalid_input
22
+ assert_raises Obvious::ContractInputError do
23
+ TestContract.new.test(Hash.new)
24
+ end
25
+ end
26
+
27
+ def test_empty_hash_return
28
+ assert_raises Obvious::DataNotFoundError do
29
+ tc = TestContract.new
30
+ tc.stub :test_alias, {} do
31
+ tc.test(id: 1)
32
+ end
33
+ end
34
+ end
35
+
36
+ def test_nil_return
37
+ assert_raises Obvious::ContractOutputError do
38
+ tc = TestContract.new
39
+ tc.stub :test_alias, nil do
40
+ tc.test(id: 1)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ class HashTest < Minitest::Test
47
+ def test_valid_has_shape
48
+ assert({id: 1}.has_shape?(id: Integer))
49
+ end
50
+
51
+ def test_invalid_has_shape
52
+ refute({id: 1}.has_shape?(id: String))
53
+ end
54
+
55
+ def test_has_shape_allow_nil_values
56
+ assert({id: nil}.has_shape?({id: String}))
57
+ end
58
+
59
+ def test_has_shape_return_invalid_field
60
+ assert_equal([false, :id], { id: 1 }.has_shape?({id: String}, true))
61
+ end
62
+ end