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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5bbb9e6927b9d2a9a2350d4b2b9fa3bb914a125d
4
- data.tar.gz: 58d8e67a612acda3b04469af9573ef2f71bfc2bb
3
+ metadata.gz: 7c5dbabbb8d69d5dadabc4592e8ac4dc6c705c26
4
+ data.tar.gz: 226889168c0093984f854e7717e5decf9658c19f
5
5
  SHA512:
6
- metadata.gz: 68014a9a7f17ad08e169fed2cb92d2582e886821e51bb7690412cec03263a97204bd1c80f127855f907ef52050abbbf42c5edf0b52e57fe64f99d3ab81233250
7
- data.tar.gz: 4283cf11f8300868265c6763fef87a6626d0cd61926bfa822fc7b23099848ba4e12d68f60fd8f59d174e05657cec2b2497657090d714aa75373429ccdb13da22
6
+ metadata.gz: 526855a236a1e2f026da812c2deca02ef02b174f7e0d37f44da02a4d5783ebc98c013c04efe2909b5cf3b451d6dafd01fa50b0f6c6a617852296bd2d0eaea0db
7
+ data.tar.gz: 1cdb32908ed57177c8f278d7af4730549a5b57fe5ad30555e32bcbf42c3e5faeb8eb3711a9f20663b2134d9062cac0a31a4e912d60270da698801fb8d2af8bbe
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ # 0.3.0
4
+ * ModelView can be used to update models
5
+
6
+
3
7
  # 0.2.0
4
8
  * ModelView can add a convenience method to the model class
5
9
 
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[:current_user] do |person, current_user|
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
@@ -1,3 +1,3 @@
1
1
  module ModelView
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
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 => {fields: {}, extends: [], includes: []}}
58
- @scopes[scope_name] = {fields: {}, extends: [], includes: []}
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 => {fields: {}, extends: [], includes: []}}
63
- @scopes[scope] ||= {fields: {}, extends: [], includes: []}
64
- @scopes[scope][:fields][field_name] = {args: args, block: block}
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
@@ -130,6 +130,5 @@ describe ModelView::Resolver do
130
130
  end
131
131
  end
132
132
 
133
-
134
133
  end
135
134
  end
@@ -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.2.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