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 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