model_view 0.0.0 → 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 +4 -4
- data/.gem_server +1 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +3 -0
- data/lib/model_view/resolver.rb +58 -0
- data/lib/model_view/version.rb +3 -0
- data/lib/model_view.rb +56 -0
- data/model_view.gemspec +26 -0
- data/spec/model_view/model_view_spec.rb +146 -0
- data/spec/model_view/resolver_spec.rb +135 -0
- data/spec/spec_helper.rb +6 -0
- metadata +19 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e1dc28eafd2cab5fd5c515e0a011d19240bb9244
|
4
|
+
data.tar.gz: 4983872ab1e955511334fae65c404d0348d5cae2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4a922409b4e094409d70a5aafd16ed365f5b9ca97cd2f4350bb5a27b373de46972b4d4dd2535fee6b4dfde9367505a92dcdc35b6e50fe31e859c2fda4b4942da
|
7
|
+
data.tar.gz: 63101e9b84430de3fb973d28301e921fcb743a22ca6feb0c6d38481b7b9259cc6b307c1efabf7d1fd43ae7014773a69066d1eb7498911c0b8ed37f85118637bb
|
data/.gem_server
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rubygems.org
|
data/Gemfile
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
module ModelView
|
2
|
+
class Resolver
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def resolve(obj, scopes, scope=nil, context={})
|
6
|
+
scope ||= ModelView::ROOT
|
7
|
+
|
8
|
+
fields = fields_for_scope(scope, scopes)
|
9
|
+
|
10
|
+
fields.each_with_object({}) do |(field_name, field_data), result|
|
11
|
+
result[field_name] = evaluate_field(obj, field_name, field_data[:args], field_data[:block], context)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
def fields_for_scope(scope, scope_data)
|
17
|
+
root_scope_fields = extract_fields(ModelView::ROOT, scope_data)
|
18
|
+
scope_fields = scope == ModelView::ROOT ? {} : extract_fields(scope, scope_data)
|
19
|
+
|
20
|
+
extended_scopes = scope_data[scope][:extends] || []
|
21
|
+
extended_fields = extended_scopes.reduce({}) do |res, scope|
|
22
|
+
res.merge(extract_fields(scope, scope_data))
|
23
|
+
end
|
24
|
+
|
25
|
+
included_scopes = (scope_data[scope][:includes] || []).reduce({}) do |res, scope_name|
|
26
|
+
res[scope_name] = fields_for_scope(scope_name, scope_data)
|
27
|
+
res
|
28
|
+
end
|
29
|
+
|
30
|
+
{}.merge(root_scope_fields)
|
31
|
+
.merge(scope_fields)
|
32
|
+
.merge(extended_fields)
|
33
|
+
.merge(included_scopes)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def evaluate_field(object, field_name, options, block, context)
|
39
|
+
if block.nil?
|
40
|
+
object.send(field_name)
|
41
|
+
else
|
42
|
+
if block.arity == 1
|
43
|
+
block.call(object)
|
44
|
+
else
|
45
|
+
arguments_for_block = options[:context].map { |key| context[key] }
|
46
|
+
block.call(object, *arguments_for_block)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def extract_fields(scope, scope_data)
|
52
|
+
scope_data[scope][:fields]
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
data/lib/model_view.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'model_view/resolver'
|
2
|
+
|
3
|
+
module ModelView
|
4
|
+
|
5
|
+
ROOT = :__root__
|
6
|
+
|
7
|
+
def field(field_name, arg={}, &block)
|
8
|
+
scope_name = @current_scope || ROOT
|
9
|
+
add_field scope_name, field_name, arg, block
|
10
|
+
end
|
11
|
+
|
12
|
+
def fields(*fields)
|
13
|
+
fields.flatten.each { |f| field f }
|
14
|
+
end
|
15
|
+
|
16
|
+
def scope(scope_name, &block)
|
17
|
+
sym_scope_name = scope_name.to_sym
|
18
|
+
add_scope(sym_scope_name)
|
19
|
+
|
20
|
+
@current_scope = sym_scope_name
|
21
|
+
instance_eval &block
|
22
|
+
@current_scope = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def include_scope(*scope)
|
26
|
+
raise Exception.new("Root scope can not include another scope") if @current_scope.nil? || @current_scope == ROOT
|
27
|
+
scope.flatten.each { |s| @scopes[@current_scope][:includes] << s }
|
28
|
+
end
|
29
|
+
|
30
|
+
def extend_scope(*scope)
|
31
|
+
raise Exception.new("Root scope can not extend another scope") if @current_scope.nil? || @current_scope == ROOT
|
32
|
+
scope.flatten.each { |s| @scopes[@current_scope][:extends] << s }
|
33
|
+
end
|
34
|
+
|
35
|
+
def scopes
|
36
|
+
@scopes
|
37
|
+
end
|
38
|
+
|
39
|
+
def as_hash(object, scope=nil, context={})
|
40
|
+
ModelView::Resolver.resolve(object, @scopes, scope || ROOT, context)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def add_scope(scope_name)
|
46
|
+
@scopes ||= {}
|
47
|
+
@scopes[scope_name] = {fields: {}, extends: [], includes: []}
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_field(scope, field_name, args, block)
|
51
|
+
@scopes ||= {}
|
52
|
+
@scopes[scope] ||= {fields: {}, extends: [], includes: []}
|
53
|
+
@scopes[scope][:fields][field_name] = {args: args, block: block}
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
data/model_view.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'model_view/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'model_view'
|
7
|
+
spec.version = ModelView::VERSION
|
8
|
+
spec.date = '2017-06-13'
|
9
|
+
spec.summary = "Composable serialisation for models"
|
10
|
+
spec.description = "Composable serialisation for models"
|
11
|
+
spec.authors = ["Martin Pretorius"]
|
12
|
+
spec.email = ['martin@offerzen.com', 'glasnoster@gmail.com']
|
13
|
+
spec.homepage = 'https://github.com/Offerzen/model_view'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "semvergen", "~> 1.9"
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
25
|
+
spec.add_development_dependency "pry", "~> 0.10"
|
26
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pry'
|
3
|
+
|
4
|
+
describe ModelView do
|
5
|
+
let(:dummy_class) { Class.new { extend ModelView } }
|
6
|
+
let(:root_scope) { :__root__ }
|
7
|
+
|
8
|
+
context "root scope" do
|
9
|
+
describe :field do
|
10
|
+
context "without a block" do
|
11
|
+
it "adds the field to the root scope" do
|
12
|
+
dummy_class.field :a_field
|
13
|
+
|
14
|
+
scope_fields = dummy_class.scopes[root_scope][:fields]
|
15
|
+
expect(scope_fields[:a_field]).to eq({args: {}, block: nil})
|
16
|
+
end
|
17
|
+
|
18
|
+
context "with arguments" do
|
19
|
+
it "adds the field to the root scope" do
|
20
|
+
dummy_class.field :a_field, {foo: 1}
|
21
|
+
|
22
|
+
scope_fields = dummy_class.scopes[root_scope][:fields]
|
23
|
+
expect(scope_fields[:a_field]).to eq({args: {foo: 1}, block: nil})
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "with a block" do
|
29
|
+
it "adds the field to the root scope" do
|
30
|
+
dummy_class.field :a_field { 1 + 1 }
|
31
|
+
|
32
|
+
scope_fields = dummy_class.scopes[root_scope][:fields]
|
33
|
+
|
34
|
+
expect(scope_fields[:a_field][:args]).to eq({})
|
35
|
+
expect(scope_fields[:a_field][:block].call).to eq(2)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe :fields do
|
41
|
+
it "adds all the fields to the root scope" do
|
42
|
+
dummy_class.fields :field_1, :field_2, :field_3
|
43
|
+
|
44
|
+
scope_fields = dummy_class.scopes[root_scope][:fields]
|
45
|
+
expect(scope_fields.keys).to eq([:field_1, :field_2, :field_3])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe :include_scope do
|
50
|
+
it "raises an error" do
|
51
|
+
expect { dummy_class.include_scope :a_field }.to raise_error "Root scope can not include another scope"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe :extend_scope do
|
56
|
+
it "raises an error" do
|
57
|
+
expect { dummy_class.extend_scope :a_field }.to raise_error "Root scope can not extend another scope"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "within a scope" do
|
63
|
+
describe :scope do
|
64
|
+
it "creates a scope" do
|
65
|
+
dummy_class.scope :a_scope { }
|
66
|
+
|
67
|
+
expect(dummy_class.scopes.keys).to include(:a_scope)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe :field do
|
72
|
+
it "adds a field inside the scope" do
|
73
|
+
dummy_class.scope :a_scope { field :foo }
|
74
|
+
|
75
|
+
scope_fields = dummy_class.scopes[:a_scope][:fields]
|
76
|
+
expect(scope_fields.keys).to include(:foo)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "changes the current scope back to root" do
|
80
|
+
dummy_class.scope :a_scope { field :foo }
|
81
|
+
dummy_class.field :bar
|
82
|
+
|
83
|
+
scope_fields = dummy_class.scopes[:a_scope][:fields]
|
84
|
+
root_scope_fields = dummy_class.scopes[root_scope][:fields]
|
85
|
+
|
86
|
+
expect(scope_fields.keys).to include(:foo)
|
87
|
+
expect(root_scope_fields.keys).to include(:bar)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe :fields do
|
92
|
+
it "adds all the fields to the current scope" do
|
93
|
+
dummy_class.scope :foo_scope { fields :field_1, :field_2, :field_3 }
|
94
|
+
|
95
|
+
scope_fields = dummy_class.scopes[:foo_scope][:fields]
|
96
|
+
expect(scope_fields.keys).to eq([:field_1, :field_2, :field_3])
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe :include_scope do
|
101
|
+
context "given a single scope" do
|
102
|
+
it "adds the scope to the current scope's includes" do
|
103
|
+
dummy_class.scope :my_scope { include_scope :foo }
|
104
|
+
expect(dummy_class.scopes[:my_scope][:includes]).to eq([:foo])
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "given an array" do
|
109
|
+
it "adds all the scopes to the current scope's includes" do
|
110
|
+
dummy_class.scope :my_scope { include_scope [:foo, :bar] }
|
111
|
+
expect(dummy_class.scopes[:my_scope][:includes]).to eq([:foo, :bar])
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context "given more than one scope" do
|
116
|
+
it "adds all the scopes to the current scope's includes" do
|
117
|
+
dummy_class.scope :my_scope { include_scope :foo, :bar }
|
118
|
+
expect(dummy_class.scopes[:my_scope][:includes]).to eq([:foo, :bar])
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe :extend_scope do
|
124
|
+
context "given a single scope" do
|
125
|
+
it "adds the scope to the current scope's extends" do
|
126
|
+
dummy_class.scope :my_scope { extend_scope :foo }
|
127
|
+
expect(dummy_class.scopes[:my_scope][:extends]).to eq([:foo])
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context "given an array" do
|
132
|
+
it "adds all the scopes to the current scope's extends" do
|
133
|
+
dummy_class.scope :my_scope { extend_scope [:foo, :bar] }
|
134
|
+
expect(dummy_class.scopes[:my_scope][:extends]).to eq([:foo, :bar])
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context "given more than one scope" do
|
139
|
+
it "adds all the scopes to the current scope's extends" do
|
140
|
+
dummy_class.scope :my_scope { extend_scope :foo, :bar }
|
141
|
+
expect(dummy_class.scopes[:my_scope][:extends]).to eq([:foo, :bar])
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pry'
|
3
|
+
|
4
|
+
describe ModelView::Resolver do
|
5
|
+
let(:scopes) do
|
6
|
+
{
|
7
|
+
__root__: {
|
8
|
+
fields: {
|
9
|
+
field1: {},
|
10
|
+
field2: {},
|
11
|
+
field3: {}
|
12
|
+
},
|
13
|
+
},
|
14
|
+
scope1: {
|
15
|
+
fields: {
|
16
|
+
field4: {block: Proc.new { |obj| obj.field4 + obj.field1 }},
|
17
|
+
field5: {},
|
18
|
+
field6: {}
|
19
|
+
},
|
20
|
+
},
|
21
|
+
scope2: {
|
22
|
+
extends: [:scope1],
|
23
|
+
fields: {
|
24
|
+
field7: {block: Proc.new { |obj, counter| obj.field7 + counter}, args: {context: [:my_counter]}},
|
25
|
+
field8: {},
|
26
|
+
field9: {}
|
27
|
+
},
|
28
|
+
},
|
29
|
+
scope3: {
|
30
|
+
includes: [:scope1],
|
31
|
+
fields: {
|
32
|
+
field10: {},
|
33
|
+
field11: {},
|
34
|
+
field12: {}
|
35
|
+
},
|
36
|
+
}
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
describe :fields_for_scope do
|
41
|
+
|
42
|
+
context "on the root scope" do
|
43
|
+
it "returns the root-level fields" do
|
44
|
+
result = ModelView::Resolver.fields_for_scope(ModelView::ROOT, scopes)
|
45
|
+
|
46
|
+
expect(result.keys).to include(*[:field1, :field2, :field3])
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "on a scope" do
|
51
|
+
it "returns the root-level fields and the scope fields" do
|
52
|
+
result = ModelView::Resolver.fields_for_scope(:scope1, scopes)
|
53
|
+
|
54
|
+
expect(result.keys).to include(*[:field1, :field2, :field3, :field4, :field5, :field6])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context "on a scope extending another scope" do
|
59
|
+
it "also includes the extended scope's fields" do
|
60
|
+
result = ModelView::Resolver.fields_for_scope(:scope2, scopes)
|
61
|
+
|
62
|
+
expect(result.keys).to include(*[:field1, :field2, :field3, :field4, :field5, :field6, :field7, :field8, :field9])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "on a scope including another scope" do
|
67
|
+
it "includes the includes scope's fields under a namespace" do
|
68
|
+
result = ModelView::Resolver.fields_for_scope(:scope3, scopes)
|
69
|
+
|
70
|
+
expect(result.keys).to include(*[:field1, :field2, :field3, :field10, :field11, :field12, :scope1])
|
71
|
+
expect(result[:scope1].keys).to include(*[:field1, :field2, :field3, :field4, :field5, :field6])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
describe :resolve do
|
78
|
+
before do
|
79
|
+
class Dummy
|
80
|
+
def field1() 1 end
|
81
|
+
def field2() 2 end
|
82
|
+
def field3() 3 end
|
83
|
+
def field4() 4 end
|
84
|
+
def field5() 5 end
|
85
|
+
def field6() 6 end
|
86
|
+
def field7() 7 end
|
87
|
+
def field8() 8 end
|
88
|
+
def field9() 9 end
|
89
|
+
def field10() 10 end
|
90
|
+
def field11() 11 end
|
91
|
+
def field12() 12 end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
let(:instance) { Dummy.new }
|
96
|
+
|
97
|
+
context "for the root scope" do
|
98
|
+
it "returns the resolved root-scope fields" do
|
99
|
+
res = ModelView::Resolver.resolve(instance, scopes)
|
100
|
+
expect(res).to eq({field1: 1, field2: 2, field3: 3})
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context "fields with a block" do
|
105
|
+
context "with an arity of one" do
|
106
|
+
it "evaluates the block" do
|
107
|
+
res = ModelView::Resolver.resolve(instance, scopes, :scope1)
|
108
|
+
expect(res[:field4]).to eq(5)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context "with an arity greater than one" do
|
113
|
+
context "no context specified in the field arguments" do
|
114
|
+
it "foo"
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
context "no a context specified in the field arguments" do
|
119
|
+
let(:dummy_context) { {foo: 1, bar: 2, my_counter: 100} }
|
120
|
+
it "injects the context into the proc" do
|
121
|
+
res = ModelView::Resolver.resolve(instance, scopes, :scope2, dummy_context)
|
122
|
+
expect(res[:field7]).to eq(107)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
it "evaluates the block" do
|
127
|
+
res = ModelView::Resolver.resolve(instance, scopes, :scope1)
|
128
|
+
expect(res[:field4]).to eq(5)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
end
|
135
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: model_view
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Martin Pretorius
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1.
|
19
|
+
version: '1.9'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1.
|
26
|
+
version: '1.9'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -82,15 +82,26 @@ dependencies:
|
|
82
82
|
version: '0.10'
|
83
83
|
description: Composable serialisation for models
|
84
84
|
email:
|
85
|
+
- martin@offerzen.com
|
85
86
|
- glasnoster@gmail.com
|
86
87
|
executables: []
|
87
88
|
extensions: []
|
88
89
|
extra_rdoc_files: []
|
89
90
|
files:
|
91
|
+
- ".gem_server"
|
90
92
|
- ".gitignore"
|
93
|
+
- CHANGELOG.md
|
94
|
+
- Gemfile
|
91
95
|
- LICENSE
|
92
96
|
- README.md
|
93
|
-
|
97
|
+
- lib/model_view.rb
|
98
|
+
- lib/model_view/resolver.rb
|
99
|
+
- lib/model_view/version.rb
|
100
|
+
- model_view.gemspec
|
101
|
+
- spec/model_view/model_view_spec.rb
|
102
|
+
- spec/model_view/resolver_spec.rb
|
103
|
+
- spec/spec_helper.rb
|
104
|
+
homepage: https://github.com/Offerzen/model_view
|
94
105
|
licenses:
|
95
106
|
- MIT
|
96
107
|
metadata: {}
|
@@ -114,4 +125,7 @@ rubygems_version: 2.6.10
|
|
114
125
|
signing_key:
|
115
126
|
specification_version: 4
|
116
127
|
summary: Composable serialisation for models
|
117
|
-
test_files:
|
128
|
+
test_files:
|
129
|
+
- spec/model_view/model_view_spec.rb
|
130
|
+
- spec/model_view/resolver_spec.rb
|
131
|
+
- spec/spec_helper.rb
|