model_view 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f688defb10471b902321d51a00192f047d6e552f
4
- data.tar.gz: f1ebe9dfbb0be1624b6587354107ea7d4756adaa
3
+ metadata.gz: e1dc28eafd2cab5fd5c515e0a011d19240bb9244
4
+ data.tar.gz: 4983872ab1e955511334fae65c404d0348d5cae2
5
5
  SHA512:
6
- metadata.gz: 3861230b60541e95df07e21e6a8ae5b62f1bdf2f0646cb7cab896ac7252d5b8ffe11c9b32bc96cb4bf65f4521b3891c57997470dd4ccdddcaf162e601f78275b
7
- data.tar.gz: 7675da184fda990dab7d96dad3e144aa3239db19734f25e65a16c04189b592cb671e4bc58580027f4f4c2b096956c9e8b9496bf494a90256f958b09dc8800881
6
+ metadata.gz: 4a922409b4e094409d70a5aafd16ed365f5b9ca97cd2f4350bb5a27b373de46972b4d4dd2535fee6b4dfde9367505a92dcdc35b6e50fe31e859c2fda4b4942da
7
+ data.tar.gz: 63101e9b84430de3fb973d28301e921fcb743a22ca6feb0c6d38481b7b9259cc6b307c1efabf7d1fd43ae7014773a69066d1eb7498911c0b8ed37f85118637bb
data/.gem_server ADDED
@@ -0,0 +1 @@
1
+ rubygems.org
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ # 0.1.0
4
+ * Initial release
5
+
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -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
@@ -0,0 +1,3 @@
1
+ module ModelView
2
+ VERSION = "0.1.0"
3
+ 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
@@ -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
@@ -0,0 +1,6 @@
1
+ require 'model_view'
2
+ require 'pry'
3
+
4
+ RSpec.configure do |config|
5
+
6
+ end
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.0.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.7'
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.7'
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
- homepage: http://rubygems.org/gems/model_view
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