obvious 0.0.7 → 0.1.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 +7 -0
- data/.github/workflows/obvious.yml +19 -0
- data/.gitignore +1 -1
- data/Gemfile.lock +34 -0
- data/README.md +10 -8
- data/lib/generators/application_generator.rb +9 -8
- data/lib/generators/descriptor.rb +22 -12
- data/lib/obvious/contract.rb +172 -127
- data/lib/obvious/entity.rb +74 -0
- data/lib/obvious/files/external/fs_plug.rb +35 -30
- data/lib/obvious/files/external/mongo_plug.rb +5 -0
- data/lib/obvious/files/external/s3_plug.rb +94 -0
- data/lib/obvious/obj.rb +38 -0
- data/lib/obvious/version.rb +1 -1
- data/lib/obvious.rb +2 -0
- data/obvious.gemspec +3 -1
- data/spec/.spec_helper.rb.swp +0 -0
- data/spec/contract_spec.rb +63 -0
- data/spec/entity_spec.rb +75 -0
- data/spec/generators/descriptor_spec.rb +34 -0
- data/spec/obj_spec.rb +64 -0
- data/spec/spec_helper.rb +3 -0
- metadata +42 -15
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4bf4de686eee48c524a3037620e21a154db8c75fce3ff8db780afc25dc376202
|
4
|
+
data.tar.gz: c4930e55f1420a9b40996fc042052f267548b3f6017055ad69355c44bad28f7e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 377a83eba55eeadeb63bdadd1a12f72a81507b5d029486add3ffba65fd81c4de929936e435bc73ac0816185bab6dff2a0aa1b4ecfcf527fa36d4dbdc13abaf81
|
7
|
+
data.tar.gz: 6f7a51c4037baa95be253c62a801f1c8204e662b089bc45bd7f6db1df94f6c39d1130625fb745a7c558111662c5baeaf59372817a077f17d14faf7af2ce0b394
|
@@ -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
|
+
bundle exec rspec
|
data/.gitignore
CHANGED
data/Gemfile.lock
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
obvious (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.5.0)
|
10
|
+
rspec (3.10.0)
|
11
|
+
rspec-core (~> 3.10.0)
|
12
|
+
rspec-expectations (~> 3.10.0)
|
13
|
+
rspec-mocks (~> 3.10.0)
|
14
|
+
rspec-core (3.10.1)
|
15
|
+
rspec-support (~> 3.10.0)
|
16
|
+
rspec-expectations (3.10.1)
|
17
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
18
|
+
rspec-support (~> 3.10.0)
|
19
|
+
rspec-mocks (3.10.2)
|
20
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
21
|
+
rspec-support (~> 3.10.0)
|
22
|
+
rspec-support (3.10.3)
|
23
|
+
|
24
|
+
PLATFORMS
|
25
|
+
ruby
|
26
|
+
x86_64-darwin-20
|
27
|
+
x86_64-linux
|
28
|
+
|
29
|
+
DEPENDENCIES
|
30
|
+
obvious!
|
31
|
+
rspec
|
32
|
+
|
33
|
+
BUNDLED WITH
|
34
|
+
2.3.4
|
data/README.md
CHANGED
@@ -6,6 +6,16 @@ independent of the app logic itself.
|
|
6
6
|
|
7
7
|
You can get a full explanation of Obvious at http://obvious.retromocha.com
|
8
8
|
|
9
|
+
|
10
|
+
# Notice:
|
11
|
+
|
12
|
+
This project is no longer under active development is only made available for historical purposes.
|
13
|
+
|
14
|
+
Right now [Brian is working on a little of this](http://brianknapp.me/now/) and [Shawn is working on a little of that](http://shawnbaden.com/now/).
|
15
|
+
|
16
|
+
Most of our spare energy goes to the [Unbranded Pocket Notebook](https://www.amazon.com/dp/B00ZGE1914/) and the [Unbranded Pocket Journal](https://www.amazon.com/dp/B016LB2XYW).
|
17
|
+
|
18
|
+
|
9
19
|
## Installation
|
10
20
|
|
11
21
|
Add this line to your application's Gemfile:
|
@@ -35,11 +45,3 @@ in the Obvious Status example app: https://github.com/RetroMocha/obvious_status.
|
|
35
45
|
Currently the footprint of the Obvious library is quite small. The most important things defined so far are the Contract class
|
36
46
|
and the Hash.has_shape? method. The rest of what makes an Obvious app interesting is the structure itself, not the libraries Obvious
|
37
47
|
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
|
@@ -30,7 +30,8 @@ module Obvious
|
|
30
30
|
|
31
31
|
puts 'Creating actions from descriptors... ' unless descriptors.length.zero?
|
32
32
|
descriptors.each do |file|
|
33
|
-
|
33
|
+
yaml = YAML.load_file(file)
|
34
|
+
descriptor = Obvious::Generators::Descriptor.new yaml
|
34
35
|
descriptor.to_file
|
35
36
|
end
|
36
37
|
|
@@ -145,27 +146,27 @@ end
|
|
145
146
|
|
146
147
|
output = %Q{require 'obvious'
|
147
148
|
|
148
|
-
class #{k}Contract < Contract
|
149
|
+
class #{k}Contract < Obvious::Contract
|
149
150
|
#{contract_defs}
|
150
151
|
end
|
151
152
|
}
|
152
153
|
|
153
|
-
snake_name =
|
154
|
+
snake_name = k.gsub(/(.)([A-Z])/,'\1_\2').downcase
|
154
155
|
|
155
|
-
filename = "#{@app.dir}/contracts/#{snake_name}
|
156
|
+
filename = "#{@app.dir}/contracts/#{snake_name}_contract.rb"
|
156
157
|
File.open(filename, 'w') {|f| f.write(output) }
|
157
158
|
|
158
|
-
output = %Q{require_relative '../../contracts/#{snake_name}
|
159
|
+
output = %Q{require_relative '../../contracts/#{snake_name}_contract'
|
159
160
|
|
160
161
|
describe #{k}Contract do
|
161
162
|
#{method_specs}
|
162
163
|
end
|
163
164
|
}
|
164
165
|
|
165
|
-
filename = "#{@app.dir}/spec/contracts/#{snake_name}
|
166
|
+
filename = "#{@app.dir}/spec/contracts/#{snake_name}_spec.rb"
|
166
167
|
File.open(filename, 'w') {|f| f.write(output) }
|
167
168
|
|
168
|
-
output = %Q{require_relative '../../contracts/#{snake_name}
|
169
|
+
output = %Q{require_relative '../../contracts/#{snake_name}_contract'
|
169
170
|
|
170
171
|
class #{k}Double
|
171
172
|
def self.create behavior
|
@@ -187,7 +188,7 @@ class #{k}_BadOutput < #{k}Contract
|
|
187
188
|
end
|
188
189
|
}
|
189
190
|
|
190
|
-
filename = "#{@app.dir}/spec/doubles/#{snake_name}
|
191
|
+
filename = "#{@app.dir}/spec/doubles/#{snake_name}_double.rb"
|
191
192
|
File.open(filename, 'w') {|f| f.write(output) }
|
192
193
|
|
193
194
|
end
|
@@ -4,22 +4,25 @@ require_relative 'helpers/application'
|
|
4
4
|
|
5
5
|
module Obvious
|
6
6
|
module Generators
|
7
|
+
class InvalidDescriptorError < StandardError; end
|
8
|
+
|
7
9
|
class Descriptor
|
8
10
|
def initialize descriptor
|
9
11
|
@descriptor = descriptor
|
10
12
|
end
|
11
13
|
|
12
14
|
def to_file
|
13
|
-
|
15
|
+
validate_descriptor
|
16
|
+
|
14
17
|
@jacks, @entities = {}, {}
|
15
18
|
@code = ''
|
16
19
|
|
17
|
-
|
20
|
+
@descriptor['Code'].each do |entry|
|
18
21
|
write_comments_for entry
|
19
22
|
process_requirements_for entry if entry['requires']
|
20
23
|
end
|
21
24
|
|
22
|
-
write_action
|
25
|
+
write_action
|
23
26
|
end
|
24
27
|
|
25
28
|
private
|
@@ -53,12 +56,12 @@ module Obvious
|
|
53
56
|
end
|
54
57
|
end # #process_requirements_for
|
55
58
|
|
56
|
-
def write_action
|
59
|
+
def write_action
|
57
60
|
jacks_data = process_jacks
|
58
61
|
requirements = require_entities
|
59
62
|
|
60
63
|
output = %Q{#{requirements}
|
61
|
-
class #{
|
64
|
+
class #{@descriptor['Action']}
|
62
65
|
|
63
66
|
def initialize #{jacks_data[:inputs]}
|
64
67
|
#{jacks_data[:assignments]} end
|
@@ -68,24 +71,24 @@ class #{action['Action']}
|
|
68
71
|
end
|
69
72
|
}
|
70
73
|
|
71
|
-
snake_name =
|
74
|
+
snake_name = @descriptor['Action'].gsub(/(.)([A-Z])/,'\1_\2').downcase
|
72
75
|
|
73
76
|
filename = "#{Obvious::Generators::Application.instance.dir}/actions/#{snake_name}.rb"
|
74
77
|
File.open(filename, 'w') {|f| f.write(output) }
|
75
78
|
|
76
|
-
|
79
|
+
output = %Q{require_relative '../../actions/#{snake_name}'
|
77
80
|
|
78
|
-
describe #{
|
81
|
+
describe #{@descriptor['Action']} do
|
79
82
|
|
80
|
-
it '#{
|
83
|
+
it '#{@descriptor['Description']}'
|
81
84
|
|
82
85
|
it 'should raise an error with invalid input'
|
83
86
|
|
84
87
|
end
|
85
|
-
}
|
88
|
+
}
|
86
89
|
|
87
|
-
|
88
|
-
|
90
|
+
filename = "#{Obvious::Generators::Application.instance.dir}/spec/actions/#{snake_name}_spec.rb"
|
91
|
+
File.open(filename, 'w') {|f| f.write(output) }
|
89
92
|
end
|
90
93
|
|
91
94
|
def process_jacks
|
@@ -116,6 +119,13 @@ end
|
|
116
119
|
|
117
120
|
entity_requires
|
118
121
|
end
|
122
|
+
|
123
|
+
def validate_descriptor
|
124
|
+
raise InvalidDescriptorError unless @descriptor
|
125
|
+
raise InvalidDescriptorError if @descriptor['Code'].nil?
|
126
|
+
raise InvalidDescriptorError if @descriptor['Action'].nil?
|
127
|
+
raise InvalidDescriptorError if @descriptor['Description'].nil?
|
128
|
+
end
|
119
129
|
end # ::Descriptor
|
120
130
|
end
|
121
131
|
end
|
data/lib/obvious/contract.rb
CHANGED
@@ -1,150 +1,161 @@
|
|
1
|
-
|
2
|
-
@@disable_override = false
|
1
|
+
module Obvious
|
3
2
|
|
4
|
-
|
5
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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]
|
96
53
|
end
|
97
54
|
|
98
|
-
|
99
|
-
else
|
100
|
-
result = self.send method
|
55
|
+
contracts( *contract_list, method )
|
101
56
|
end
|
102
57
|
|
103
|
-
#
|
104
|
-
#
|
105
|
-
|
106
|
-
|
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
|
107
81
|
end
|
108
82
|
|
109
|
-
#
|
110
|
-
if
|
111
|
-
|
112
|
-
|
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 ContractInputError, "incorrect input data format field #{error_field}"
|
99
|
+
end
|
100
|
+
|
101
|
+
result = self.send method, input
|
113
102
|
else
|
103
|
+
result = self.send method
|
104
|
+
end
|
105
|
+
|
106
|
+
# check output
|
107
|
+
# output should never be nil
|
108
|
+
if result == nil
|
114
109
|
raise ContractOutputError, 'incorrect output data format'
|
115
110
|
end
|
116
|
-
end
|
117
111
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
112
|
+
if result === {}
|
113
|
+
raise 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 ContractOutputError, 'incorrect output data format'
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
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 ContractOutputError, "incorrect output data format field #{error_field}"
|
133
|
+
end
|
125
134
|
end
|
135
|
+
|
136
|
+
return result
|
137
|
+
end
|
138
|
+
raise 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 ContractOutputError, 'incorrect output data format'
|
126
145
|
end
|
127
146
|
|
128
147
|
return result
|
129
148
|
end
|
130
|
-
raise ContractOutputError, 'incorrect output data format'
|
131
|
-
end
|
132
149
|
|
133
|
-
|
134
|
-
|
135
|
-
unless
|
136
|
-
raise ContractOutputError,
|
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 ContractOutputError, "incorrect output data format field #{error_field}"
|
137
154
|
end
|
138
155
|
|
139
|
-
|
140
|
-
end
|
141
|
-
|
142
|
-
# we want result to be output_shape's shape
|
143
|
-
unless result.has_shape? output_shape
|
144
|
-
raise ContractOutputError, 'incorrect output data format'
|
156
|
+
result
|
145
157
|
end
|
146
158
|
|
147
|
-
result
|
148
159
|
end
|
149
160
|
|
150
161
|
end
|
@@ -153,7 +164,7 @@ end
|
|
153
164
|
class Hash
|
154
165
|
# Checks if a hash has a certain structure.
|
155
166
|
# h = { k1: 1, k2: "1" }
|
156
|
-
# h.has_shape?(k1:
|
167
|
+
# h.has_shape?(k1: Integer, k2: String)
|
157
168
|
# #=> true
|
158
169
|
# h.has_shape?(k1: Class, k2: String)
|
159
170
|
# #=> false
|
@@ -162,17 +173,47 @@ class Hash
|
|
162
173
|
# shape = { k1: Array, k2: { k3: Module } }
|
163
174
|
# h.has_shape?(shape)
|
164
175
|
# #=> true
|
165
|
-
def has_shape?(shape)
|
176
|
+
def has_shape?(shape, return_field = false)
|
177
|
+
return_value = lambda { |r, f|
|
178
|
+
if return_field
|
179
|
+
return r, f
|
180
|
+
else
|
181
|
+
return r
|
182
|
+
end
|
183
|
+
}
|
184
|
+
|
166
185
|
# I added an empty check
|
167
186
|
if self.empty?
|
168
|
-
return shape.empty
|
169
|
-
end
|
170
|
-
|
171
|
-
|
187
|
+
return return_value.call shape.empty?, nil
|
188
|
+
end
|
189
|
+
|
190
|
+
self.each do |k, v|
|
191
|
+
return return_value.call false, k if shape[k] == nil
|
192
|
+
end
|
193
|
+
|
194
|
+
shape.each do |k, v|
|
172
195
|
# hash_value
|
173
196
|
hv = self[k]
|
174
|
-
|
197
|
+
return return_value.call false, k unless self.has_key? k
|
198
|
+
|
199
|
+
next if hv === nil
|
200
|
+
|
201
|
+
if Hash === hv
|
202
|
+
return hv.has_shape?(v, return_field)
|
203
|
+
else
|
204
|
+
return return_value.call false, k unless v === hv
|
205
|
+
end
|
175
206
|
end
|
207
|
+
|
208
|
+
return_value.call true, nil
|
209
|
+
end
|
210
|
+
|
211
|
+
def nil_fields? list
|
212
|
+
list.each do |field|
|
213
|
+
return true, field unless self[field]
|
214
|
+
end
|
215
|
+
|
216
|
+
return false, nil
|
176
217
|
end
|
177
218
|
end
|
178
219
|
|
@@ -181,3 +222,7 @@ end
|
|
181
222
|
|
182
223
|
class ContractOutputError < StandardError
|
183
224
|
end
|
225
|
+
|
226
|
+
class DataNotFoundError < StandardError
|
227
|
+
end
|
228
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
|
2
|
+
module Obvious
|
3
|
+
|
4
|
+
module EntityMixin
|
5
|
+
class << self
|
6
|
+
def included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
attr_reader :shape
|
13
|
+
attr_reader :validations
|
14
|
+
|
15
|
+
def value name, type
|
16
|
+
name = name.to_sym
|
17
|
+
@shape ||= {}
|
18
|
+
@shape[name] = type
|
19
|
+
define_method(name) { @values[name] }
|
20
|
+
end
|
21
|
+
|
22
|
+
def validation name, method
|
23
|
+
name = "#{name}_validation".to_sym
|
24
|
+
@validations ||= []
|
25
|
+
@validations << name
|
26
|
+
define_method(name) { instance_exec &method }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class ShapeError < StandardError; end
|
32
|
+
class TypeError < StandardError; end
|
33
|
+
class ValidationError < StandardError; end
|
34
|
+
|
35
|
+
class Entity
|
36
|
+
include EntityMixin
|
37
|
+
|
38
|
+
def initialize input
|
39
|
+
shape = self.class.shape
|
40
|
+
validations = self.class.validations || []
|
41
|
+
|
42
|
+
self.class.shape.freeze
|
43
|
+
self.class.validations.freeze
|
44
|
+
|
45
|
+
@values = {}
|
46
|
+
input.each do |k, v|
|
47
|
+
unless shape[k]
|
48
|
+
raise Obvious::ShapeError.new "Invalid input field: #{k}"
|
49
|
+
else
|
50
|
+
@values[k] = v
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
@values.freeze # this might need to be recursive?
|
55
|
+
|
56
|
+
shape.each do |k, v|
|
57
|
+
unless @values[k].class == v
|
58
|
+
msg = "Validation Error: Invalid value for #{k}, should be a #{v}"
|
59
|
+
raise Obvious::TypeError.new msg
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
validations.each { |method| send method }
|
64
|
+
|
65
|
+
freeze
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_hash
|
69
|
+
{}.tap {|h| @values.each { |k, v| h[k] = v } }
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
@@ -6,13 +6,19 @@ class FsPlug
|
|
6
6
|
@filename = filename
|
7
7
|
end
|
8
8
|
|
9
|
-
def
|
10
|
-
# open the file
|
9
|
+
def load_data
|
11
10
|
contents = File.read @filename
|
11
|
+
JSON.parse contents, :symbolize_names => true
|
12
|
+
end
|
12
13
|
|
14
|
+
def save_data input
|
15
|
+
json_data = JSON.pretty_generate input
|
16
|
+
File.open(@filename, 'w') {|f| f.write(json_data) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def save input
|
13
20
|
data = []
|
14
|
-
|
15
|
-
query = JSON.parse contents, :symbolize_names => true
|
21
|
+
query = load_data
|
16
22
|
|
17
23
|
new_element = true if input[:id] == -1 # by convention set new element flag if id == -1
|
18
24
|
|
@@ -27,51 +33,37 @@ class FsPlug
|
|
27
33
|
end
|
28
34
|
|
29
35
|
# add data to the list if it's a new element
|
30
|
-
|
31
|
-
|
36
|
+
if new_element
|
37
|
+
input[:id] = max_id + 1
|
38
|
+
data << input
|
39
|
+
end
|
32
40
|
|
33
|
-
|
34
|
-
json_data = JSON.pretty_generate data
|
35
|
-
File.open(@filename, 'w') {|f| f.write(json_data) }
|
41
|
+
save_data data
|
36
42
|
|
37
43
|
# return the transformed data
|
38
44
|
input
|
39
45
|
end
|
40
46
|
|
41
47
|
def list
|
42
|
-
|
43
|
-
contents = File.read @filename
|
44
|
-
|
45
|
-
# parse the json list
|
46
|
-
data = JSON.parse contents, :symbolize_names => true
|
47
|
-
|
48
|
-
# return the transformed data
|
49
|
-
data
|
48
|
+
load_data
|
50
49
|
end
|
51
50
|
|
52
51
|
def get input
|
53
|
-
# open the file
|
54
|
-
contents = File.read @filename
|
55
|
-
|
56
52
|
data = []
|
57
|
-
|
58
|
-
query = JSON.parse contents, :symbolize_names => true
|
53
|
+
query = load_data
|
59
54
|
|
60
55
|
# transform the data if needed
|
61
56
|
query.each do |h|
|
62
57
|
return h if h[:id] == input[:id]
|
63
58
|
end
|
64
59
|
|
65
|
-
|
60
|
+
raise Exception.new 'no object found'
|
66
61
|
end
|
67
62
|
|
68
63
|
def remove input
|
69
|
-
# open the file
|
70
|
-
contents = File.read @filename
|
71
|
-
|
72
64
|
data = []
|
73
65
|
# parse the json list
|
74
|
-
query =
|
66
|
+
query = load_data
|
75
67
|
|
76
68
|
# transform the data if needed
|
77
69
|
query.each do |h|
|
@@ -80,15 +72,28 @@ class FsPlug
|
|
80
72
|
end
|
81
73
|
end
|
82
74
|
|
83
|
-
|
84
|
-
json_data = JSON.pretty_generate data
|
85
|
-
File.open(@filename, 'w') {|f| f.write(json_data) }
|
75
|
+
save_data data
|
86
76
|
|
87
77
|
# return true on success
|
88
78
|
true
|
89
79
|
end
|
90
80
|
|
81
|
+
def find input
|
82
|
+
data = []
|
83
|
+
query = load_data
|
84
|
+
|
85
|
+
key = input.keys[0]
|
86
|
+
|
87
|
+
query.each do |h|
|
88
|
+
if input[key] == h[key]
|
89
|
+
return h
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
raise Exception.new 'no object found'
|
94
|
+
end
|
91
95
|
end
|
92
96
|
|
93
97
|
|
94
98
|
|
99
|
+
|
@@ -1,5 +1,10 @@
|
|
1
1
|
require 'moped'
|
2
2
|
|
3
|
+
# To get the mongo system to work you need to have a counters collection. The
|
4
|
+
# code here will increment the seq field which we use to set the id. If you are
|
5
|
+
# having problems, you probably need to create the collection and add entries
|
6
|
+
# for each other collection you want to have an id sequence.
|
7
|
+
|
3
8
|
class MongoPlug
|
4
9
|
def initialize collection
|
5
10
|
@collection = collection
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'aws/s3'
|
3
|
+
|
4
|
+
class S3Plug
|
5
|
+
include AWS::S3
|
6
|
+
|
7
|
+
def initialize input
|
8
|
+
@filename = input[:filename]
|
9
|
+
@bucket = input[:bucket]
|
10
|
+
|
11
|
+
# you can test locally with the fake-s3 gem: https://github.com/jubos/fake-s3
|
12
|
+
# or just swap out for the filesystem plug locally
|
13
|
+
s3_key = ENV['S3_KEY'] || '123'
|
14
|
+
s3_secret = ENV['S3_SECRET'] || 'abc'
|
15
|
+
s3_server = ENV['S3_SERVER'] || '0.0.0.0'
|
16
|
+
s3_port = ENV['S3_PORT'] || '10001'
|
17
|
+
|
18
|
+
AWS::S3::Base.establish_connection!(:access_key_id => s3_key,
|
19
|
+
:secret_access_key => s3_secret,
|
20
|
+
:server => s3_server,
|
21
|
+
:port => s3_port)
|
22
|
+
end
|
23
|
+
|
24
|
+
def load_data
|
25
|
+
contents = S3Object.value @filename, @bucket
|
26
|
+
JSON.parse contents, :symbolize_names => true
|
27
|
+
end
|
28
|
+
|
29
|
+
def save_data data
|
30
|
+
json_data = JSON.pretty_generate data
|
31
|
+
S3Object.store @filename, json_data, @bucket
|
32
|
+
end
|
33
|
+
|
34
|
+
def save input
|
35
|
+
data = []
|
36
|
+
result = load_data
|
37
|
+
new_element = true if input[:id] == -1 # by convention set new element flag if id == -1
|
38
|
+
|
39
|
+
max_id = -1
|
40
|
+
# transform the data if needed
|
41
|
+
result.each do |h|
|
42
|
+
if input[:id] == h[:id]
|
43
|
+
h = input
|
44
|
+
end
|
45
|
+
max_id = h[:id] if h[:id] > max_id
|
46
|
+
data << h
|
47
|
+
end
|
48
|
+
|
49
|
+
# add data to the list if it's a new element
|
50
|
+
if new_element
|
51
|
+
input[:id] = max_id + 1
|
52
|
+
data << input
|
53
|
+
end
|
54
|
+
|
55
|
+
save_data data
|
56
|
+
|
57
|
+
# return the transformed data
|
58
|
+
input
|
59
|
+
end
|
60
|
+
|
61
|
+
def list
|
62
|
+
load_data
|
63
|
+
end
|
64
|
+
|
65
|
+
def get input
|
66
|
+
data = []
|
67
|
+
result = load_data
|
68
|
+
|
69
|
+
# transform the data if needed
|
70
|
+
result.each do |h|
|
71
|
+
return h if h[:id] == input[:id]
|
72
|
+
end
|
73
|
+
|
74
|
+
{}
|
75
|
+
end
|
76
|
+
|
77
|
+
def remove input
|
78
|
+
data = []
|
79
|
+
result = load_data
|
80
|
+
|
81
|
+
# transform the data if needed
|
82
|
+
result.each do |h|
|
83
|
+
unless h[:id] == input[:id]
|
84
|
+
data << h
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
save_data data
|
89
|
+
|
90
|
+
# return true on success
|
91
|
+
true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
data/lib/obvious/obj.rb
ADDED
@@ -0,0 +1,38 @@
|
|
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][1]
|
20
|
+
raise ArgumentError.new "invalid type for #{k} expected #{input[k][1]}"
|
21
|
+
end
|
22
|
+
|
23
|
+
block_input[input[k][0]] = v
|
24
|
+
end
|
25
|
+
|
26
|
+
input.each do |k,v|
|
27
|
+
if block_input[v[0]].nil?
|
28
|
+
raise ArgumentError.new "missing input field #{k}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
self.instance_exec block_input, &block
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/obvious/version.rb
CHANGED
data/lib/obvious.rb
CHANGED
data/obvious.gemspec
CHANGED
@@ -9,11 +9,13 @@ Gem::Specification.new do |gem|
|
|
9
9
|
gem.authors = ["Brian Knapp"]
|
10
10
|
gem.email = ["brianknapp@gmail.com"]
|
11
11
|
gem.description = "A set of tools to build apps using the Obvious Architecture"
|
12
|
-
gem.summary = "
|
12
|
+
gem.summary = "Clean Architecture framework"
|
13
13
|
gem.homepage = "http://obvious.retromocha.com/"
|
14
14
|
|
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"
|
19
21
|
end
|
Binary file
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require_relative '../lib/obvious/contract'
|
3
|
+
|
4
|
+
|
5
|
+
describe Hash do
|
6
|
+
describe '#has_shape?' do
|
7
|
+
it 'should return true for a valid shape' do
|
8
|
+
expect({ id: 1 }.has_shape?(id: Integer)).to be true
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should return false for an invalid shape' do
|
12
|
+
expect({ id: 1 }.has_shape?(id: String)).to be false
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should retrn the invalid field if return_field flag is set' do
|
16
|
+
expect({ id: 1 }.has_shape?({id: String}, true)).to eq [false, :id]
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should allow for nil values to be returned' do
|
20
|
+
expect({ id: nil }.has_shape?({id: String})).to be true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class TestContract < Obvious::Contract
|
26
|
+
contract_for :test, {
|
27
|
+
input: { id: Integer },
|
28
|
+
output: { id: Integer, value: String }
|
29
|
+
}
|
30
|
+
|
31
|
+
def test input
|
32
|
+
{ id: 1, value: 'this is a test' }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe Obvious::Contract do
|
37
|
+
|
38
|
+
describe "#call_method" do
|
39
|
+
it 'should return the correct output for valid input and output shapes' do
|
40
|
+
tc = TestContract.new
|
41
|
+
result = tc.test id: 1
|
42
|
+
expect(result).to eq id: 1, value: 'this is a test'
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should raise a contract input error with bad input' do
|
46
|
+
tc = TestContract.new
|
47
|
+
expect { tc.test Hash.new }.to raise_error ContractInputError
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should raise a DataNotFound error if {} is returned' do
|
51
|
+
tc = TestContract.new
|
52
|
+
allow(tc).to receive(:test_alias).and_return({})
|
53
|
+
expect { tc.test id: 1 }.to raise_error DataNotFoundError
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should raise a contract output error if nil is returned' do
|
57
|
+
tc = TestContract.new
|
58
|
+
allow(tc).to receive(:test_alias).and_return(nil)
|
59
|
+
expect { tc.test id: 1 }.to raise_error ContractOutputError
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
data/spec/entity_spec.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require_relative '../lib/obvious/entity'
|
2
|
+
|
3
|
+
class Thing < Obvious::Entity
|
4
|
+
value :id, Integer
|
5
|
+
value :name, String
|
6
|
+
end
|
7
|
+
|
8
|
+
class Thing2 < Obvious::Entity
|
9
|
+
value :foo , String
|
10
|
+
|
11
|
+
validation :something, Proc.new {
|
12
|
+
if foo != "hello world"
|
13
|
+
msg = "Validation Error: Invalid value for foo, should be 'hello world'"
|
14
|
+
raise Obvious::ValidationError.new msg
|
15
|
+
end
|
16
|
+
}
|
17
|
+
|
18
|
+
def modify_foo
|
19
|
+
@values[:foo] = 100
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
class Thing3 < Obvious::Entity
|
25
|
+
value :foo , String
|
26
|
+
|
27
|
+
validation :something, Proc.new {
|
28
|
+
@values[:foo] = 12
|
29
|
+
}
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
# To test the entity, we are going to use classes that inherit from it instead
|
34
|
+
# of poking at it directly. In this case, I think that makes the most sense.
|
35
|
+
describe Thing do
|
36
|
+
it 'should create a valid object with valid input' do
|
37
|
+
input = { name: 'Thing', id: 1 }
|
38
|
+
t = Thing.new input
|
39
|
+
expect(t.name).to eq 'Thing'
|
40
|
+
expect(t.id).to eq 1
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should raise an error with invalid input types' do
|
44
|
+
input = { name: nil, id: nil }
|
45
|
+
expect { Thing.new input }.to raise_error Obvious::TypeError
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should raise an error with extra fields in input' do
|
49
|
+
input = { name: 'Thing', id: 1, extra: 'should explode' }
|
50
|
+
expect { Thing.new input }.to raise_error Obvious::ShapeError
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should raise an error when a method tries to modify a value' do
|
54
|
+
t = Thing2.new foo: 'hello world'
|
55
|
+
expect { t.modify_foo }.to raise_error RuntimeError
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#to_hash' do
|
59
|
+
it 'should return a hash representation of the object' do
|
60
|
+
input = { name: 'Thing', id: 1 }
|
61
|
+
t = Thing.new input
|
62
|
+
expect(t.to_hash).to eq input
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe 'validation' do
|
67
|
+
it 'should raise a validation error on a failed validation' do
|
68
|
+
expect { Thing2.new foo: 'not valid I promise!' }.to raise_error Obvious::ValidationError
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should raise an error when trying to modify an object in a validation' do
|
72
|
+
expect { Thing3.new foo: 'hello world' }.to raise_error RuntimeError
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative '../../lib/generators/descriptor'
|
2
|
+
|
3
|
+
require File.expand_path('spec/spec_helper')
|
4
|
+
|
5
|
+
module Obvious
|
6
|
+
module Generators
|
7
|
+
describe Descriptor do
|
8
|
+
subject {Descriptor.new(yaml_file)}
|
9
|
+
|
10
|
+
describe "#to_file" do
|
11
|
+
|
12
|
+
context "when the descriptor is empty" do
|
13
|
+
let( :yaml_file ) { {} }
|
14
|
+
|
15
|
+
it "should raise a meaningful error" do
|
16
|
+
expect {subject.to_file}.to raise_error(InvalidDescriptorError)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
["Action", "Code", "Description"].each do |section|
|
21
|
+
context "when the '#{section}' section is omitted" do
|
22
|
+
let( :yaml_file ) {
|
23
|
+
{"Action" => "Jackson", "Description" => "This is something"}.delete(section)
|
24
|
+
}
|
25
|
+
|
26
|
+
it "should raise a meaningful error" do
|
27
|
+
expect {subject.to_file}.to raise_error(InvalidDescriptorError)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/spec/obj_spec.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require_relative '../lib/obvious/obj'
|
2
|
+
|
3
|
+
class TestObj
|
4
|
+
include Obvious::Obj
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@local = 'set!'
|
8
|
+
end
|
9
|
+
|
10
|
+
define :defined_method, with_foo: [:foo, String], also_bar: [:bar, Integer] do |input|
|
11
|
+
input
|
12
|
+
end
|
13
|
+
|
14
|
+
define :defined_local do |input|
|
15
|
+
@local
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
describe Obvious::Obj do
|
21
|
+
|
22
|
+
before do
|
23
|
+
@test = TestObj.new
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'self.define' do
|
27
|
+
it 'should do the right thing with correct input' do
|
28
|
+
result = @test.defined_method with_foo: 'hello', also_bar: 12
|
29
|
+
expect(result).to eq foo: 'hello', bar: 12
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should have access to instance variables' do
|
33
|
+
result = @test.defined_local
|
34
|
+
expect(result).to eq 'set!'
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should raise an error for missing parameters' do
|
38
|
+
expect { @test.defined_method with_foo: 'hello' }.to raise_error { |error|
|
39
|
+
expect(error).to be_a ArgumentError
|
40
|
+
expect(error.message).to eq 'missing input field also_bar'
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should raise an error for extra parameters' do
|
45
|
+
expect { @test.defined_method with_foo: 'hello', also_bar: 12, and_extra: 'this is extra!' }.to raise_error { |error|
|
46
|
+
expect(error).to be_a ArgumentError
|
47
|
+
expect(error.message).to eq 'invalid input field and_extra'
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should raise an error for invalid types' do
|
52
|
+
expect { @test.defined_method with_foo: 1, also_bar: 12 }.to raise_error { |error|
|
53
|
+
expect(error).to be_a ArgumentError
|
54
|
+
expect(error.message).to eq 'invalid type for with_foo expected String'
|
55
|
+
}
|
56
|
+
|
57
|
+
expect {@test.defined_method with_foo: 'hello', also_bar: nil }.to raise_error { |error|
|
58
|
+
expect(error).to be_a ArgumentError
|
59
|
+
expect(error.message).to eq 'invalid type for also_bar expected Integer'
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
@@ -1,16 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: obvious
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
5
|
-
prerelease:
|
4
|
+
version: 0.1.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Brian Knapp
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
13
|
-
dependencies:
|
11
|
+
date: 2022-01-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
14
27
|
description: A set of tools to build apps using the Obvious Architecture
|
15
28
|
email:
|
16
29
|
- brianknapp@gmail.com
|
@@ -19,8 +32,10 @@ executables:
|
|
19
32
|
extensions: []
|
20
33
|
extra_rdoc_files: []
|
21
34
|
files:
|
22
|
-
- .
|
35
|
+
- ".github/workflows/obvious.yml"
|
36
|
+
- ".gitignore"
|
23
37
|
- Gemfile
|
38
|
+
- Gemfile.lock
|
24
39
|
- LICENSE.txt
|
25
40
|
- README.md
|
26
41
|
- Rakefile
|
@@ -30,35 +45,47 @@ files:
|
|
30
45
|
- lib/generators/helpers/application.rb
|
31
46
|
- lib/obvious.rb
|
32
47
|
- lib/obvious/contract.rb
|
48
|
+
- lib/obvious/entity.rb
|
33
49
|
- lib/obvious/files/Rakefile
|
34
50
|
- lib/obvious/files/external/fs_plug.rb
|
35
51
|
- lib/obvious/files/external/mongo_plug.rb
|
36
52
|
- lib/obvious/files/external/mysql_plug.rb
|
53
|
+
- lib/obvious/files/external/s3_plug.rb
|
54
|
+
- lib/obvious/obj.rb
|
37
55
|
- lib/obvious/version.rb
|
38
56
|
- obvious.gemspec
|
57
|
+
- spec/.spec_helper.rb.swp
|
58
|
+
- spec/contract_spec.rb
|
59
|
+
- spec/entity_spec.rb
|
60
|
+
- spec/generators/descriptor_spec.rb
|
61
|
+
- spec/obj_spec.rb
|
62
|
+
- spec/spec_helper.rb
|
39
63
|
homepage: http://obvious.retromocha.com/
|
40
64
|
licenses: []
|
65
|
+
metadata: {}
|
41
66
|
post_install_message:
|
42
67
|
rdoc_options: []
|
43
68
|
require_paths:
|
44
69
|
- lib
|
45
70
|
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
-
none: false
|
47
71
|
requirements:
|
48
|
-
- -
|
72
|
+
- - ">="
|
49
73
|
- !ruby/object:Gem::Version
|
50
74
|
version: '0'
|
51
75
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
-
none: false
|
53
76
|
requirements:
|
54
|
-
- -
|
77
|
+
- - ">="
|
55
78
|
- !ruby/object:Gem::Version
|
56
79
|
version: '0'
|
57
80
|
requirements: []
|
58
|
-
|
59
|
-
rubygems_version: 1.8.17
|
81
|
+
rubygems_version: 3.0.3
|
60
82
|
signing_key:
|
61
|
-
specification_version:
|
62
|
-
summary:
|
63
|
-
test_files:
|
64
|
-
|
83
|
+
specification_version: 4
|
84
|
+
summary: Clean Architecture framework
|
85
|
+
test_files:
|
86
|
+
- spec/.spec_helper.rb.swp
|
87
|
+
- spec/contract_spec.rb
|
88
|
+
- spec/entity_spec.rb
|
89
|
+
- spec/generators/descriptor_spec.rb
|
90
|
+
- spec/obj_spec.rb
|
91
|
+
- spec/spec_helper.rb
|