model_view 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|