model_view 0.2.0 → 0.3.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/CHANGELOG.md +4 -0
- data/README.md +38 -1
- data/lib/model_view/updater.rb +76 -0
- data/lib/model_view/version.rb +1 -1
- data/lib/model_view.rb +40 -5
- data/spec/model_view/model_view_spec.rb +66 -0
- data/spec/model_view/resolver_spec.rb +0 -1
- data/spec/model_view/updater_spec.rb +119 -0
- metadata +4 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7c5dbabbb8d69d5dadabc4592e8ac4dc6c705c26
|
4
|
+
data.tar.gz: 226889168c0093984f854e7717e5decf9658c19f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 526855a236a1e2f026da812c2deca02ef02b174f7e0d37f44da02a4d5783ebc98c013c04efe2909b5cf3b451d6dafd01fa50b0f6c6a617852296bd2d0eaea0db
|
7
|
+
data.tar.gz: 1cdb32908ed57177c8f278d7af4730549a5b57fe5ad30555e32bcbf42c3e5faeb8eb3711a9f20663b2134d9062cac0a31a4e912d60270da698801fb8d2af8bbe
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -33,7 +33,7 @@ field :name
|
|
33
33
|
|
34
34
|
field(:first_name) { |person| person.name.split(' ').first }
|
35
35
|
|
36
|
-
field :is_current_user, context
|
36
|
+
field :is_current_user, {context: current_user} do |person, current_user|
|
37
37
|
person == current_user
|
38
38
|
end
|
39
39
|
```
|
@@ -130,3 +130,40 @@ PersonView.as_hash(person, {context: {current_user: current_user}, scope: :all})
|
|
130
130
|
is_current_user: true
|
131
131
|
}
|
132
132
|
```
|
133
|
+
|
134
|
+
#### Updating models
|
135
|
+
`ModelView` can also be used to update models. This is achieved by creating setters.
|
136
|
+
|
137
|
+
The easiest way to create a setter is to set the `setter` flag to true when defining a field:
|
138
|
+
```ruby
|
139
|
+
field :name, setter: true
|
140
|
+
```
|
141
|
+
|
142
|
+
Setters can also be defined outside of the `field` macro:
|
143
|
+
```ruby
|
144
|
+
setter :name
|
145
|
+
```
|
146
|
+
|
147
|
+
Setters defined in this way are naive (`obj.send("#{field}=", value)`) and will not automatically call the `save` method on the model. See below on how to create an `after_update` hook.
|
148
|
+
|
149
|
+
Finally, for more complex updaters, a block can be passed:
|
150
|
+
```ruby
|
151
|
+
setter(:name) do |obj, name|
|
152
|
+
first_name, last_name = name.split(" ")
|
153
|
+
obj.first_name = first_name
|
154
|
+
obj.last_name = last_name
|
155
|
+
obj.save!
|
156
|
+
end
|
157
|
+
```
|
158
|
+
|
159
|
+
To avoid having to explicitly save the model in every block, one can define an `after_save` block:
|
160
|
+
```ruby
|
161
|
+
after_save { |obj| obj.save! }
|
162
|
+
|
163
|
+
field :telephone_number, setter: true
|
164
|
+
setter(:name) do |obj, name|
|
165
|
+
first_name, last_name = name.split(" ")
|
166
|
+
obj.first_name = first_name
|
167
|
+
obj.last_name = last_name
|
168
|
+
end
|
169
|
+
```
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module ModelView
|
2
|
+
class Updater
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def update(obj, scopes, data, scope=nil)
|
6
|
+
scope ||= ModelView::ROOT
|
7
|
+
|
8
|
+
setters = setters_for_scope(scope, scopes)
|
9
|
+
|
10
|
+
data.each do |key, value|
|
11
|
+
setter = if !setters[key].nil?
|
12
|
+
setter_for_key(setters, key)
|
13
|
+
else
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
|
17
|
+
setter.call(obj, value) if setter
|
18
|
+
end
|
19
|
+
|
20
|
+
after_update = after_update_for_scope(scope, scopes)
|
21
|
+
after_update.call(obj) if after_update
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def setter_for_key(setters, key)
|
26
|
+
return nil if setters[key].nil?
|
27
|
+
if setters[key][:block]
|
28
|
+
setters[key][:block]
|
29
|
+
else
|
30
|
+
lambda { |obj, value| obj.send("#{key}=", value) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def after_update_for_scope(scope, scopes)
|
35
|
+
extended_scopes = scopes[scope][:extends] || []
|
36
|
+
first_extended_after_update = extended_scopes.reduce(nil) do |res, s|
|
37
|
+
res || extract_after_update(s, scopes)
|
38
|
+
end
|
39
|
+
|
40
|
+
extract_after_update(scope, scopes) ||
|
41
|
+
first_extended_after_update ||
|
42
|
+
extract_after_update(ModelView::ROOT, scopes)
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
def setters_for_scope(scope, scope_data)
|
47
|
+
root_scope_setters = extract_setters(ModelView::ROOT, scope_data)
|
48
|
+
scope_setters = scope == ModelView::ROOT ? {} : extract_setters(scope, scope_data)
|
49
|
+
|
50
|
+
extended_scopes = scope_data[scope][:extends] || []
|
51
|
+
extended_setters = extended_scopes.reduce({}) do |res, scope|
|
52
|
+
res.merge(extract_setters(scope, scope_data))
|
53
|
+
end
|
54
|
+
|
55
|
+
included_setters = (scope_data[scope][:includes] || []).reduce({}) do |res, scope_name|
|
56
|
+
res[scope_name] = setters_for_scope(scope_name, scope_data)
|
57
|
+
res
|
58
|
+
end
|
59
|
+
|
60
|
+
{}.merge(root_scope_setters)
|
61
|
+
.merge(scope_setters)
|
62
|
+
.merge(extended_setters)
|
63
|
+
.merge(included_setters)
|
64
|
+
end
|
65
|
+
|
66
|
+
def extract_setters(scope, scope_data)
|
67
|
+
scope_data[scope][:setters]
|
68
|
+
end
|
69
|
+
|
70
|
+
def extract_after_update(scope, scope_data)
|
71
|
+
scope_data[scope][:after_update]
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/model_view/version.rb
CHANGED
data/lib/model_view.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'model_view/resolver'
|
2
|
+
require 'model_view/updater'
|
2
3
|
|
3
4
|
module ModelView
|
4
5
|
|
@@ -22,6 +23,11 @@ module ModelView
|
|
22
23
|
@current_scope = nil
|
23
24
|
end
|
24
25
|
|
26
|
+
def setter(field_name, arg={}, &block)
|
27
|
+
scope_name = @current_scope || ROOT
|
28
|
+
add_setter scope_name, field_name, arg, block
|
29
|
+
end
|
30
|
+
|
25
31
|
def include_scope(*scope)
|
26
32
|
raise Exception.new("Root scope can not include another scope") if @current_scope.nil? || @current_scope == ROOT
|
27
33
|
scope.flatten.each { |s| @scopes[@current_scope][:includes] << s }
|
@@ -32,6 +38,11 @@ module ModelView
|
|
32
38
|
scope.flatten.each { |s| @scopes[@current_scope][:extends] << s }
|
33
39
|
end
|
34
40
|
|
41
|
+
def after_update(&block)
|
42
|
+
scope_name = @current_scope || ROOT
|
43
|
+
add_after_update scope_name, block
|
44
|
+
end
|
45
|
+
|
35
46
|
def scopes
|
36
47
|
@scopes
|
37
48
|
end
|
@@ -42,6 +53,11 @@ module ModelView
|
|
42
53
|
ModelView::Resolver.resolve(object, @scopes, scope , context)
|
43
54
|
end
|
44
55
|
|
56
|
+
def update(object, data, opts={})
|
57
|
+
scope = opts[:scope] || ROOT
|
58
|
+
ModelView::Updater.update(object, @scopes, data , scope)
|
59
|
+
end
|
60
|
+
|
45
61
|
def model(model_class)
|
46
62
|
model_view_class = self
|
47
63
|
model_class.class_eval do
|
@@ -53,15 +69,34 @@ module ModelView
|
|
53
69
|
|
54
70
|
private
|
55
71
|
|
72
|
+
def new_opts
|
73
|
+
{fields: {}, extends: [], includes: [], setters: {}, after_update: nil}
|
74
|
+
end
|
75
|
+
|
76
|
+
def add_after_update(scope, block)
|
77
|
+
@scopes ||= {ROOT => new_opts}
|
78
|
+
@scopes[scope] ||= new_opts
|
79
|
+
@scopes[scope][:after_update] = block
|
80
|
+
end
|
81
|
+
|
56
82
|
def add_scope(scope_name)
|
57
|
-
@scopes ||= {ROOT =>
|
58
|
-
@scopes[scope_name] =
|
83
|
+
@scopes ||= {ROOT => new_opts}
|
84
|
+
@scopes[scope_name] = new_opts
|
59
85
|
end
|
60
86
|
|
61
87
|
def add_field(scope, field_name, args, block)
|
62
|
-
@scopes ||= {ROOT =>
|
63
|
-
@scopes[scope] ||=
|
64
|
-
|
88
|
+
@scopes ||= {ROOT => new_opts}
|
89
|
+
@scopes[scope] ||= new_opts
|
90
|
+
if args[:setter]
|
91
|
+
add_setter(scope, field_name, {}, nil)
|
92
|
+
end
|
93
|
+
@scopes[scope][:fields][field_name] = {args: args.select{ |k| k != :setter}, block: block}
|
94
|
+
end
|
95
|
+
|
96
|
+
def add_setter(scope, field_name, args, block)
|
97
|
+
@scopes ||= {ROOT => new_opts}
|
98
|
+
@scopes[scope] ||= new_opts
|
99
|
+
@scopes[scope][:setters][field_name] = {args: args, block: block}
|
65
100
|
end
|
66
101
|
|
67
102
|
end
|
@@ -23,6 +23,18 @@ describe ModelView do
|
|
23
23
|
expect(scope_fields[:a_field]).to eq({args: {foo: 1}, block: nil})
|
24
24
|
end
|
25
25
|
end
|
26
|
+
|
27
|
+
context "with setter set to true" do
|
28
|
+
it "adds the field and setter to the root scope" do
|
29
|
+
dummy_class.field :a_field, {setter: true}
|
30
|
+
|
31
|
+
scope_fields = dummy_class.scopes[root_scope][:fields]
|
32
|
+
expect(scope_fields[:a_field]).to eq({args: {}, block: nil})
|
33
|
+
|
34
|
+
scope_setters = dummy_class.scopes[root_scope][:setters]
|
35
|
+
expect(scope_setters[:a_field]).to eq({args: {}, block: nil})
|
36
|
+
end
|
37
|
+
end
|
26
38
|
end
|
27
39
|
|
28
40
|
context "with a block" do
|
@@ -57,6 +69,37 @@ describe ModelView do
|
|
57
69
|
expect { dummy_class.extend_scope :a_field }.to raise_error "Root scope can not extend another scope"
|
58
70
|
end
|
59
71
|
end
|
72
|
+
|
73
|
+
describe :setter do
|
74
|
+
context "without a block" do
|
75
|
+
it "adds the setter to the root scope" do
|
76
|
+
dummy_class.setter :a_field
|
77
|
+
|
78
|
+
scope_setters = dummy_class.scopes[root_scope][:setters]
|
79
|
+
expect(scope_setters[:a_field]).to eq({args: {}, block: nil})
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "with a block" do
|
84
|
+
it "adds the seter to the root scope" do
|
85
|
+
dummy_class.setter :a_field { 1 + 1 }
|
86
|
+
|
87
|
+
scope_setters = dummy_class.scopes[root_scope][:setters]
|
88
|
+
|
89
|
+
expect(scope_setters[:a_field][:args]).to eq({})
|
90
|
+
expect(scope_setters[:a_field][:block].call).to eq(2)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe :after_update do
|
96
|
+
it "adds an after_update block" do
|
97
|
+
dummy_class.after_update { |obj| obj.save }
|
98
|
+
|
99
|
+
after_update = dummy_class.scopes[root_scope][:after_update]
|
100
|
+
expect(after_update).to be_a(Proc)
|
101
|
+
end
|
102
|
+
end
|
60
103
|
end
|
61
104
|
|
62
105
|
context "within a scope" do
|
@@ -142,6 +185,29 @@ describe ModelView do
|
|
142
185
|
end
|
143
186
|
end
|
144
187
|
end
|
188
|
+
|
189
|
+
describe :setter do
|
190
|
+
context "without a block" do
|
191
|
+
it "adds the setter to the root scope" do
|
192
|
+
dummy_class.scope :my_scope { setter :a_field }
|
193
|
+
|
194
|
+
scope_setters = dummy_class.scopes[:my_scope][:setters]
|
195
|
+
expect(scope_setters[:a_field]).to eq({args: {}, block: nil})
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
context "with a block" do
|
200
|
+
it "adds the seter to the root scope" do
|
201
|
+
dummy_class.scope :my_scope { setter :a_field { 1 + 1 } }
|
202
|
+
|
203
|
+
scope_setters = dummy_class.scopes[:my_scope][:setters]
|
204
|
+
|
205
|
+
expect(scope_setters[:a_field][:args]).to eq({})
|
206
|
+
expect(scope_setters[:a_field][:block].call).to eq(2)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
145
211
|
end
|
146
212
|
|
147
213
|
describe :model do
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pry'
|
3
|
+
|
4
|
+
describe ModelView::Updater do
|
5
|
+
let(:scopes) do
|
6
|
+
{
|
7
|
+
__root__: {
|
8
|
+
fields: {
|
9
|
+
field1: {},
|
10
|
+
field2: {},
|
11
|
+
field3: {}
|
12
|
+
},
|
13
|
+
setters: {
|
14
|
+
field1: {},
|
15
|
+
field3: {block: Proc.new { |obj, data| obj.field3 = data + 2 }}
|
16
|
+
},
|
17
|
+
after_update: Proc.new { |obj| obj.save }
|
18
|
+
},
|
19
|
+
scope1: {
|
20
|
+
fields: {
|
21
|
+
field4: {block: Proc.new { |obj| obj.field4 + obj.field1 }},
|
22
|
+
field5: {},
|
23
|
+
field6: {}
|
24
|
+
},
|
25
|
+
setters: {
|
26
|
+
field4: {block: Proc.new { |obj, data| obj.field4 = data }},
|
27
|
+
field5: {},
|
28
|
+
field6: {}
|
29
|
+
}
|
30
|
+
},
|
31
|
+
scope2: {
|
32
|
+
extends: [:scope1],
|
33
|
+
fields: {
|
34
|
+
field7: {},
|
35
|
+
field8: {},
|
36
|
+
field9: {}
|
37
|
+
},
|
38
|
+
setters: {
|
39
|
+
field7: {}
|
40
|
+
}
|
41
|
+
},
|
42
|
+
scope3: {
|
43
|
+
includes: [:scope1],
|
44
|
+
fields: {
|
45
|
+
field10: {},
|
46
|
+
field11: {},
|
47
|
+
field12: {}
|
48
|
+
},
|
49
|
+
setters: {
|
50
|
+
field10: {}
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
describe :update do
|
57
|
+
before do
|
58
|
+
class Dummy
|
59
|
+
attr_accessor *(1..12).map { |n| "field#{n}".to_sym }
|
60
|
+
|
61
|
+
def save
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
let(:instance) { Dummy.new }
|
68
|
+
|
69
|
+
context "for the root scope" do
|
70
|
+
let(:data) { {field1: 1, field2: 2, field3: 2} }
|
71
|
+
context "for a setter without a block" do
|
72
|
+
it "sets the values with setters" do
|
73
|
+
ModelView::Updater.update(instance, scopes, data)
|
74
|
+
expect(instance.field1).to eq(1)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "does not set the values without setters" do
|
78
|
+
ModelView::Updater.update(instance, scopes, data)
|
79
|
+
expect(instance.field2).to be_nil
|
80
|
+
end
|
81
|
+
|
82
|
+
it "uses a block to set the value, if available" do
|
83
|
+
ModelView::Updater.update(instance, scopes, data)
|
84
|
+
expect(instance.field3).to eq(4)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "runs the after_update block, if provided" do
|
88
|
+
expect(instance).to receive(:save)
|
89
|
+
ModelView::Updater.update(instance, scopes, data)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context "on a scope" do
|
95
|
+
it "includes the root level setters" do
|
96
|
+
data = {field1: 1, field4: 4}
|
97
|
+
ModelView::Updater.update(instance, scopes, data, :scope1)
|
98
|
+
|
99
|
+
expect(instance.field1).to eq(1)
|
100
|
+
expect(instance.field4).to eq(4)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context "on a scope extending another scope" do
|
105
|
+
it "also includes the extended scope's setters" do
|
106
|
+
data = {field7: 7, field4: 4}
|
107
|
+
ModelView::Updater.update(instance, scopes, data, :scope2)
|
108
|
+
|
109
|
+
expect(instance.field7).to eq(7)
|
110
|
+
expect(instance.field4).to eq(4)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "on a scope including another scope" do
|
115
|
+
it "also includes the included scope's setters"
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
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.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Martin Pretorius
|
@@ -96,10 +96,12 @@ files:
|
|
96
96
|
- README.md
|
97
97
|
- lib/model_view.rb
|
98
98
|
- lib/model_view/resolver.rb
|
99
|
+
- lib/model_view/updater.rb
|
99
100
|
- lib/model_view/version.rb
|
100
101
|
- model_view.gemspec
|
101
102
|
- spec/model_view/model_view_spec.rb
|
102
103
|
- spec/model_view/resolver_spec.rb
|
104
|
+
- spec/model_view/updater_spec.rb
|
103
105
|
- spec/spec_helper.rb
|
104
106
|
homepage: https://github.com/Offerzen/model_view
|
105
107
|
licenses:
|
@@ -128,4 +130,5 @@ summary: Composable serialisation for models
|
|
128
130
|
test_files:
|
129
131
|
- spec/model_view/model_view_spec.rb
|
130
132
|
- spec/model_view/resolver_spec.rb
|
133
|
+
- spec/model_view/updater_spec.rb
|
131
134
|
- spec/spec_helper.rb
|