obvious 0.0.8 → 0.2.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 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