jsonapi_compliable 0.5.7 → 0.6.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: 51079670b0c1dedbb613140f18d466467aac8176
4
- data.tar.gz: 25aa3bb0cafd200e8508d7e9492ca6334f2451d4
3
+ metadata.gz: 2f26c6ffeb63e0bd72ef36d3b2c86c6931a8f4fa
4
+ data.tar.gz: ae546cd488fe425c049efac300602efb8234ddce
5
5
  SHA512:
6
- metadata.gz: a11eafba78c92d9b13d547b0ab43d6fec831a4f0f872c79d122a289f76be3a22dd16c53e78fb7f2a242c6b4bec92b3795fd3c0d8a8fee4b7d75788572200a938
7
- data.tar.gz: a654eea7228308a1a057522fa6c0b5783a93c7644ef4c5b444100a1c543cfc3365e03a40d5f8aa9038b8557790dcfd60067ad8e0f6665e04a4636ec0f40dc1fa
6
+ metadata.gz: 595f742f0a782fddc1e161981d359fb9bce7976b014e556c8ce84f3f2c0365b0b035ff2ebb22a247b9776cb05b33e97bab180b613ed8167995004b3e258084a8
7
+ data.tar.gz: 5dea3994b495f6b1b455f23a10bf9169630b54ef0886082a0e99888cdf665f7dd621294dc6776a43c0e54c241f9fd983070ff432dd188f49a45d75cab745e1f4
@@ -1,7 +1,7 @@
1
1
  PATH
2
- remote: ../
2
+ remote: ..
3
3
  specs:
4
- jsonapi_compliable (0.5.5)
4
+ jsonapi_compliable (0.5.7)
5
5
  jsonapi-serializable (~> 0.1)
6
6
 
7
7
  GEM
@@ -205,4 +205,4 @@ DEPENDENCIES
205
205
  sqlite3
206
206
 
207
207
  BUNDLED WITH
208
- 1.13.6
208
+ 1.14.6
@@ -1,7 +1,7 @@
1
1
  PATH
2
- remote: ../
2
+ remote: ..
3
3
  specs:
4
- jsonapi_compliable (0.5.5)
4
+ jsonapi_compliable (0.5.7)
5
5
  jsonapi-serializable (~> 0.1)
6
6
 
7
7
  GEM
@@ -209,4 +209,4 @@ DEPENDENCIES
209
209
  sqlite3
210
210
 
211
211
  BUNDLED WITH
212
- 1.13.6
212
+ 1.14.6
@@ -4,6 +4,7 @@ require "jsonapi_compliable/resource"
4
4
  require "jsonapi_compliable/query"
5
5
  require "jsonapi_compliable/sideload"
6
6
  require "jsonapi_compliable/scope"
7
+ require "jsonapi_compliable/deserializer"
7
8
  require "jsonapi_compliable/scoping/base"
8
9
  require "jsonapi_compliable/scoping/sort"
9
10
  require "jsonapi_compliable/scoping/paginate"
@@ -18,6 +19,9 @@ require "jsonapi_compliable/stats/payload"
18
19
  require "jsonapi_compliable/util/include_params"
19
20
  require "jsonapi_compliable/util/field_params"
20
21
  require "jsonapi_compliable/util/hash"
22
+ require "jsonapi_compliable/util/relationship_payload"
23
+ require "jsonapi_compliable/util/persistence"
24
+ require "jsonapi_compliable/util/validation_response"
21
25
 
22
26
  # require correct jsonapi-rb before extensions
23
27
  if defined?(Rails)
@@ -30,8 +34,7 @@ require "jsonapi_compliable/extensions/extra_attribute"
30
34
  require "jsonapi_compliable/extensions/boolean_attribute"
31
35
 
32
36
  module JsonapiCompliable
33
- autoload :Base, 'jsonapi_compliable/base'
34
- autoload :Deserializable, 'jsonapi_compliable/deserializable'
37
+ autoload :Base, 'jsonapi_compliable/base'
35
38
 
36
39
  def self.included(klass)
37
40
  klass.instance_eval do
@@ -17,6 +17,10 @@ module JsonapiCompliable
17
17
  raise 'you must override #sideload in an adapter subclass'
18
18
  end
19
19
 
20
+ def transaction
21
+ raise 'you must override #transaction in an adapter subclass, it must yield'
22
+ end
23
+
20
24
  def resolve(scope)
21
25
  scope
22
26
  end
@@ -39,9 +39,42 @@ module JsonapiCompliable
39
39
  scope.to_a
40
40
  end
41
41
 
42
+ def transaction(model_class)
43
+ model_class.transaction do
44
+ yield
45
+ end
46
+ end
47
+
42
48
  def sideloading_module
43
49
  JsonapiCompliable::Adapters::ActiveRecordSideloading
44
50
  end
51
+
52
+ def associate(parent, child, association_name, association_type)
53
+ if association_type == :has_many
54
+ parent.association(association_name).loaded!
55
+ parent.association(association_name).add_to_target(child, :skip_callbacks)
56
+ else
57
+ child.send("#{association_name}=", parent)
58
+ end
59
+ end
60
+
61
+ def create(model_class, create_params)
62
+ instance = model_class.new(create_params)
63
+ instance.save
64
+ instance
65
+ end
66
+
67
+ def update(model_class, update_params)
68
+ instance = model_class.find(update_params.delete(:id))
69
+ instance.update_attributes(update_params)
70
+ instance
71
+ end
72
+
73
+ def destroy(model_class, id)
74
+ instance = model_class.find(id)
75
+ instance.destroy
76
+ instance
77
+ end
45
78
  end
46
79
  end
47
80
  end
@@ -4,7 +4,7 @@ module JsonapiCompliable
4
4
  def has_many(association_name, scope: nil, resource:, foreign_key:, primary_key: :id, &blk)
5
5
  _scope = scope
6
6
 
7
- allow_sideload association_name, resource: resource do
7
+ allow_sideload association_name, type: :has_many, resource: resource, foreign_key: foreign_key, primary_key: primary_key do
8
8
  scope do |parents|
9
9
  parent_ids = parents.map { |p| p.send(primary_key) }
10
10
  _scope.call.where(foreign_key => parent_ids.uniq.compact)
@@ -27,7 +27,7 @@ module JsonapiCompliable
27
27
  def belongs_to(association_name, scope: nil, resource:, foreign_key:, primary_key: :id, &blk)
28
28
  _scope = scope
29
29
 
30
- allow_sideload association_name, resource: resource do
30
+ allow_sideload association_name, type: :belongs_to, resource: resource, foreign_key: foreign_key, primary_key: primary_key do
31
31
  scope do |parents|
32
32
  parent_ids = parents.map { |p| p.send(foreign_key) }
33
33
  _scope.call.where(primary_key => parent_ids.uniq.compact)
@@ -16,6 +16,10 @@ module JsonapiCompliable
16
16
  def sideload(scope, includes)
17
17
  scope
18
18
  end
19
+
20
+ def transaction
21
+ yield
22
+ end
19
23
  end
20
24
  end
21
25
  end
@@ -1,7 +1,6 @@
1
1
  module JsonapiCompliable
2
2
  module Base
3
3
  extend ActiveSupport::Concern
4
- include Deserializable
5
4
 
6
5
  MAX_PAGE_SIZE = 1_000
7
6
 
@@ -32,7 +31,6 @@ module JsonapiCompliable
32
31
  @query_hash ||= query.to_hash[resource.type]
33
32
  end
34
33
 
35
- # TODO pass controller and action name here to guard
36
34
  def wrap_context
37
35
  if self.class._jsonapi_compliable
38
36
  resource.with_context(self, action_name.to_sym) do
@@ -45,6 +43,30 @@ module JsonapiCompliable
45
43
  resource.build_scope(scope, query, opts)
46
44
  end
47
45
 
46
+ def deserialized_params
47
+ @deserialized_params ||= JsonapiCompliable::Deserializer.new(params, request.env)
48
+ end
49
+
50
+ def jsonapi_create
51
+ created = resource.transaction do
52
+ resource.persist_with_relationships \
53
+ deserialized_params.meta,
54
+ deserialized_params.attributes,
55
+ deserialized_params.relationships
56
+ end
57
+ Util::ValidationResponse.new(created, deserialized_params)
58
+ end
59
+
60
+ def jsonapi_update
61
+ updated = resource.transaction do
62
+ resource.persist_with_relationships \
63
+ deserialized_params.meta,
64
+ deserialized_params.attributes,
65
+ deserialized_params.relationships
66
+ end
67
+ Util::ValidationResponse.new(updated, deserialized_params)
68
+ end
69
+
48
70
  def perform_render_jsonapi(opts)
49
71
  JSONAPI::Serializable::Renderer.render(opts.delete(:jsonapi), opts)
50
72
  end
@@ -54,7 +76,7 @@ module JsonapiCompliable
54
76
  opts = default_jsonapi_render_options.merge(opts)
55
77
  opts = Util::RenderOptions.generate(scope, query_hash, opts)
56
78
  opts[:expose][:context] = self
57
- opts[:include] = forced_includes if force_includes?
79
+ opts[:include] = deserialized_params.include_directive if force_includes?
58
80
  perform_render_jsonapi(opts)
59
81
  end
60
82
 
@@ -65,33 +87,8 @@ module JsonapiCompliable
65
87
  end
66
88
  end
67
89
 
68
- # Legacy
69
- # TODO: This nastiness likely goes away once jsonapi standardizes
70
- # a spec for nested relationships.
71
- # See: https://github.com/json-api/json-api/issues/1089
72
- def forced_includes(data = nil)
73
- data = raw_params[:data] unless data
74
-
75
- {}.tap do |forced|
76
- (data[:relationships] || {}).each_pair do |relation_name, relation|
77
- if relation[:data].is_a?(Array)
78
- forced[relation_name] = {}
79
- relation[:data].each do |datum|
80
- forced[relation_name].deep_merge!(forced_includes(datum))
81
- end
82
- else
83
- forced[relation_name] = forced_includes(relation[:data])
84
- end
85
- end
86
- end
87
- end
88
-
89
- # Legacy
90
90
  def force_includes?
91
- return false unless defined?(Rails)
92
-
93
- %w(PUT PATCH POST).include?(request.method) and
94
- raw_params.try(:[], :data).try(:[], :relationships).present?
91
+ not params[:data].nil?
95
92
  end
96
93
 
97
94
  module ClassMethods
@@ -0,0 +1,139 @@
1
+ class JsonapiCompliable::Deserializer
2
+ def initialize(payload, env)
3
+ @payload = payload
4
+ @env = env
5
+ end
6
+
7
+ def data
8
+ @payload[:data]
9
+ end
10
+
11
+ def id
12
+ data[:id]
13
+ end
14
+
15
+ def attributes
16
+ @attributes ||= raw_attributes.tap do |hash|
17
+ hash.merge!(id: id) if id
18
+ end
19
+ end
20
+
21
+ def attributes=(attrs)
22
+ @attributes = attrs
23
+ end
24
+
25
+ def method
26
+ case @env['REQUEST_METHOD']
27
+ when 'POST' then :create
28
+ when 'PUT', 'PATCH' then :update
29
+ when 'DELETE' then :destroy
30
+ end
31
+ end
32
+
33
+ def meta
34
+ {
35
+ type: data[:type],
36
+ temp_id: data[:'temp-id'],
37
+ method: method
38
+ }
39
+ end
40
+
41
+ def relationships
42
+ @relationships ||= process_relationships(raw_relationships)
43
+ end
44
+
45
+ def included
46
+ @payload[:included] || []
47
+ end
48
+
49
+ def include_directive(memo = {}, relationship_node = nil)
50
+ relationship_node ||= relationships
51
+
52
+ relationship_node.each_pair do |name, relationship_payload|
53
+ arrayified = [relationship_payload].flatten
54
+ next if arrayified.all? { |rp| removed?(rp) }
55
+
56
+ memo[name] ||= {}
57
+ deep_merge!(memo[name], sub_directives(memo[name], arrayified))
58
+ end
59
+
60
+ memo
61
+ end
62
+
63
+ private
64
+
65
+ def removed?(relationship_payload)
66
+ method = relationship_payload[:meta][:method]
67
+ [:disassociate, :destroy].include?(method)
68
+ end
69
+
70
+ def sub_directives(memo, relationship_payloads)
71
+ {}.tap do |subs|
72
+ relationship_payloads.each do |rp|
73
+ sub_directive = include_directive(memo, rp[:relationships])
74
+ deep_merge!(subs, sub_directive)
75
+ end
76
+ end
77
+ end
78
+
79
+ def deep_merge!(a, b)
80
+ JsonapiCompliable::Util::Hash.deep_merge!(a, b)
81
+ end
82
+
83
+ def process_relationships(relationship_hash)
84
+ {}.tap do |hash|
85
+ relationship_hash.each_pair do |name, relationship_payload|
86
+ name = name.to_sym
87
+
88
+ if relationship_payload[:data]
89
+ hash[name] = process_relationship(relationship_payload[:data])
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ def process_relationship(relationship_data)
96
+ if relationship_data.is_a?(Array)
97
+ relationship_data.map do |rd|
98
+ process_relationship_datum(rd)
99
+ end
100
+ else
101
+ process_relationship_datum(relationship_data)
102
+ end
103
+ end
104
+
105
+ def process_relationship_datum(datum)
106
+ temp_id = datum[:'temp-id']
107
+ included_object = included.find do |i|
108
+ next unless i[:type] == datum[:type]
109
+
110
+ (i[:id] && i[:id] == datum[:id]) ||
111
+ (i[:'temp-id'] && i[:'temp-id'] == temp_id)
112
+ end
113
+ included_object ||= {}
114
+ included_object[:relationships] ||= {}
115
+
116
+ attributes = included_object[:attributes] || {}
117
+ attributes[:id] = datum[:id] if datum[:id]
118
+ relationships = process_relationships(included_object[:relationships] || {})
119
+
120
+
121
+ {
122
+ meta: {
123
+ jsonapi_type: datum[:type],
124
+ temp_id: temp_id,
125
+ method: datum[:method].try(:to_sym)
126
+ },
127
+ attributes: attributes,
128
+ relationships: relationships
129
+ }
130
+ end
131
+
132
+ def raw_attributes
133
+ data[:attributes] || {}
134
+ end
135
+
136
+ def raw_relationships
137
+ data[:relationships] || {}
138
+ end
139
+ end
@@ -50,6 +50,10 @@ module JsonapiCompliable
50
50
  }
51
51
  end
52
52
 
53
+ def self.model(klass)
54
+ config[:model] = klass
55
+ end
56
+
53
57
  def self.sort(&blk)
54
58
  config[:sorting] = blk
55
59
  end
@@ -92,6 +96,7 @@ module JsonapiCompliable
92
96
  stats: {},
93
97
  sorting: nil,
94
98
  pagination: nil,
99
+ model: nil,
95
100
  adapter: Adapters::Abstract.new
96
101
  }
97
102
  end
@@ -115,6 +120,24 @@ module JsonapiCompliable
115
120
  Scope.new(base, self, query, opts)
116
121
  end
117
122
 
123
+ def create(create_params)
124
+ adapter.create(model, create_params)
125
+ end
126
+
127
+ def update(update_params)
128
+ adapter.update(model, update_params)
129
+ end
130
+
131
+ def destroy(id)
132
+ adapter.destroy(model, id)
133
+ end
134
+
135
+ def persist_with_relationships(meta, attributes, relationships)
136
+ persistence = JsonapiCompliable::Util::Persistence \
137
+ .new(self, meta, attributes, relationships)
138
+ persistence.run
139
+ end
140
+
118
141
  def association_names
119
142
  @association_names ||= begin
120
143
  if sideloading
@@ -190,6 +213,10 @@ module JsonapiCompliable
190
213
  self.class.config[:default_filters]
191
214
  end
192
215
 
216
+ def model
217
+ self.class.config[:model]
218
+ end
219
+
193
220
  def adapter
194
221
  self.class.config[:adapter]
195
222
  end
@@ -197,5 +224,11 @@ module JsonapiCompliable
197
224
  def resolve(scope)
198
225
  adapter.resolve(scope)
199
226
  end
227
+
228
+ def transaction
229
+ adapter.transaction(model) do
230
+ yield
231
+ end
232
+ end
200
233
  end
201
234
  end
@@ -6,18 +6,28 @@ module JsonapiCompliable
6
6
  :sideloads,
7
7
  :scope_proc,
8
8
  :assign_proc,
9
- :grouper
9
+ :grouper,
10
+ :foreign_key,
11
+ :primary_key,
12
+ :type
10
13
 
11
- def initialize(name, opts)
14
+ def initialize(name, type: nil, resource: nil, polymorphic: false, primary_key: :id, foreign_key: nil)
12
15
  @name = name
13
- @resource_class = (opts[:resource] || Class.new(Resource))
16
+ @resource_class = (resource || Class.new(Resource))
14
17
  @sideloads = {}
15
- @polymorphic = !!opts[:polymorphic]
18
+ @polymorphic = !!polymorphic
16
19
  @polymorphic_groups = {} if polymorphic?
20
+ @primary_key = primary_key
21
+ @foreign_key = foreign_key
22
+ @type = type
17
23
 
18
24
  extend @resource_class.config[:adapter].sideloading_module
19
25
  end
20
26
 
27
+ def resource
28
+ @resource ||= resource_class.new
29
+ end
30
+
21
31
  def polymorphic?
22
32
  @polymorphic == true
23
33
  end
@@ -30,6 +40,10 @@ module JsonapiCompliable
30
40
  @assign_proc = blk
31
41
  end
32
42
 
43
+ def associate(parent, child)
44
+ resource_class.config[:adapter].associate(parent, child, name, type)
45
+ end
46
+
33
47
  def group_by(&grouper)
34
48
  @grouper = grouper
35
49
  end
@@ -10,6 +10,11 @@ module JsonapiCompliable
10
10
  collection
11
11
  end
12
12
 
13
+ def self.deep_merge!(hash, other)
14
+ merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
15
+ hash.merge!(other, &merger)
16
+ end
17
+
13
18
  def self.deep_dup(hash)
14
19
  if hash.respond_to?(:deep_dup)
15
20
  hash.deep_dup
@@ -0,0 +1,114 @@
1
+ class JsonapiCompliable::Util::Persistence
2
+ def initialize(resource, meta, attributes, relationships)
3
+ @resource = resource
4
+ @meta = meta
5
+ @attributes = attributes
6
+ @relationships = relationships
7
+ end
8
+
9
+ # belongs_to must be processed before/separately from has_many -
10
+ # we need to know the primary key value of the parent before
11
+ # persisting the child
12
+ #
13
+ # Flow:
14
+ # * process parents
15
+ # * update attributes to reflect parent primary keys
16
+ # * persist current object
17
+ # * associate temp id with current object
18
+ # * associate parent objects with current object
19
+ # * process children
20
+ # * associate children
21
+ # * return current object
22
+ def run
23
+ parents = process_belongs_to(@relationships)
24
+ update_foreign_key_for_parents(parents)
25
+
26
+ persisted = persist_object(@meta[:method], @attributes)
27
+ assign_temp_id(persisted, @meta[:temp_id])
28
+ associate_parents(persisted, parents)
29
+
30
+ children = process_has_many(@relationships) do |x|
31
+ update_foreign_key(persisted, x[:attributes], x)
32
+ end
33
+
34
+ associate_children(persisted, children)
35
+ persisted unless @meta[:method] == :destroy
36
+ end
37
+
38
+ # The child's attributes should be modified to nil-out the
39
+ # foreign_key when the parent is being destroyed or disassociated
40
+ def update_foreign_key(parent_object, attrs, x)
41
+ if [:destroy, :disassociate].include?(x[:meta][:method])
42
+ attrs[x[:foreign_key]] = nil
43
+ else
44
+ attrs[x[:foreign_key]] = parent_object.send(x[:primary_key])
45
+ end
46
+ end
47
+
48
+ def update_foreign_key_for_parents(parents)
49
+ parents.each do |x|
50
+ update_foreign_key(x[:object], @attributes, x)
51
+ end
52
+ end
53
+
54
+ def associate_parents(object, parents)
55
+ parents.each do |x|
56
+ x[:sideload].associate(x[:object], object) if x[:object] && object
57
+ end
58
+ end
59
+
60
+ def associate_children(object, children)
61
+ children.each do |x|
62
+ x[:sideload].associate(object, x[:object]) if x[:object] && object
63
+ end
64
+ end
65
+
66
+ def persist_object(method, attributes)
67
+ case method
68
+ when :destroy
69
+ @resource.destroy(attributes[:id])
70
+ when :disassociate, nil
71
+ @resource.update(attributes)
72
+ else
73
+ @resource.send(method, attributes)
74
+ end
75
+ end
76
+
77
+ def process_has_many(relationships)
78
+ [].tap do |processed|
79
+ iterate(except: [:belongs_to]) do |x|
80
+ yield x
81
+ x[:object] = x[:sideload].resource
82
+ .persist_with_relationships(x[:meta], x[:attributes], x[:relationships])
83
+ processed << x
84
+ end
85
+ end
86
+ end
87
+
88
+ def process_belongs_to(relationships)
89
+ [].tap do |processed|
90
+ iterate(only: [:belongs_to]) do |x|
91
+ x[:object] = x[:sideload].resource
92
+ .persist_with_relationships(x[:meta], x[:attributes], x[:relationships])
93
+ processed << x
94
+ end
95
+ end
96
+ end
97
+
98
+ def assign_temp_id(object, temp_id)
99
+ object.instance_variable_set(:@_jsonapi_temp_id, temp_id)
100
+ end
101
+
102
+ private
103
+
104
+ def iterate(only: [], except: [])
105
+ opts = {
106
+ resource: @resource,
107
+ relationships: @relationships,
108
+ }.merge(only: only, except: except)
109
+
110
+ JsonapiCompliable::Util::RelationshipPayload.iterate(opts) do |x|
111
+ yield x
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,55 @@
1
+ module JsonapiCompliable
2
+ module Util
3
+ class RelationshipPayload
4
+ attr_reader :resource, :payload
5
+
6
+ def self.iterate(resource:, relationships: {}, only: [], except: [])
7
+ instance = new(resource, relationships, only: only, except: except)
8
+ instance.iterate do |sideload, relationship_data, sub_relationships|
9
+ yield sideload, relationship_data, sub_relationships
10
+ end
11
+ end
12
+
13
+ def initialize(resource, payload, only: [], except: [])
14
+ @resource = resource
15
+ @payload = payload
16
+ @only = only
17
+ @except = except
18
+ end
19
+
20
+ def iterate
21
+ payload.each_pair do |relationship_name, relationship_payload|
22
+ if sl = resource.sideload(relationship_name.to_sym)
23
+ if should_yield?(sl.type)
24
+ if relationship_payload.is_a?(Array)
25
+ relationship_payload.each do |rp|
26
+ yield payload_for(sl, rp)
27
+ end
28
+ else
29
+ yield payload_for(sl, relationship_payload)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def should_yield?(type)
39
+ (@only.length > 0 && @only.include?(type)) ||
40
+ (@except.length > 0 && !@except.include?(type))
41
+ end
42
+
43
+ def payload_for(sideload, relationship_payload)
44
+ {
45
+ sideload: sideload,
46
+ primary_key: sideload.primary_key,
47
+ foreign_key: sideload.foreign_key,
48
+ attributes: relationship_payload[:attributes],
49
+ meta: relationship_payload[:meta],
50
+ relationships: relationship_payload[:relationships]
51
+ }
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,20 @@
1
+ class JsonapiCompliable::Util::ValidationResponse
2
+ attr_reader :object
3
+
4
+ def initialize(object, deserialized_params)
5
+ @object = object
6
+ @deserialized_params = deserialized_params
7
+ end
8
+
9
+ def success?
10
+ if object.respond_to?(:errors)
11
+ object.errors.blank?
12
+ else
13
+ true
14
+ end
15
+ end
16
+
17
+ def to_a
18
+ [object, success?]
19
+ end
20
+ end
@@ -1,3 +1,3 @@
1
1
  module JsonapiCompliable
2
- VERSION = "0.5.7"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -0,0 +1,93 @@
1
+ class JsonapiCompliable::Write
2
+ attr_reader :resource
3
+
4
+ def initialize(opts = {})
5
+ @resource = opts[:resource]
6
+ end
7
+
8
+ def persist
9
+
10
+ end
11
+ end
12
+
13
+ resource.persist(obj, persistence_query, opts)
14
+
15
+ p = Persistence.new(obj, resource: resource, params: params, opts: opts)
16
+ p.persist
17
+
18
+
19
+ allow_nested_write :books, resource: BookResource do
20
+ # rdefault to using BookResource in these
21
+ # hooks, but user can override to save
22
+ # via author
23
+ #
24
+ # assign temp id AFTER
25
+ # must handle NESTING genre
26
+ create do |author, book_params|
27
+ book_params[:author_id] = author.id
28
+ BookResource.create(book_params)
29
+ #book = Book.new(book_params)
30
+ #book.author_id = author.id
31
+ # instance variable set new id?
32
+ # # should be outside this method
33
+ #book.save!
34
+ #book
35
+ end
36
+
37
+ # HOW TO REUSE THIS ON /books
38
+ # should it be? i guess update, but not create
39
+ update do |author, book_params|
40
+ BookResource.update(book_params)
41
+ #book = Book.find(book_params[:id])
42
+ #book.update_attributes(book_params)
43
+ #book
44
+ end
45
+
46
+ # Might be fair to say you can remove rel
47
+ # but not destroy it
48
+ # if needed, add destroy hook later
49
+ delete do |author, book_params|
50
+ book_params[:author_id] = nil
51
+ BookResource.update(book_params)
52
+ end
53
+
54
+ destroy do |author, book_params|
55
+ BookResource.destroy(book_params)
56
+ end
57
+ end
58
+
59
+ #def jsonapi_scope(scope, opts = {})
60
+ #resource.build_scope(scope, query, opts)
61
+ #end
62
+
63
+ # controller
64
+ #
65
+ # book = Book.new(params)
66
+ #
67
+ # if jsonapi_persist(book)
68
+ # render_jsonapi(book)
69
+ # else
70
+ # render_errors_for(book)
71
+ # end
72
+
73
+
74
+
75
+ class Writer
76
+ def initialize(parent)
77
+ end
78
+
79
+ def doit
80
+ relationships.each do |name, data|
81
+ if has_many?
82
+ created = Child.new(data, fk: fk)
83
+ created.TEMP_ID = data['temp_id']
84
+ created = created.save
85
+ mapping {data['temp_id'] => created.id}
86
+ parent.name 4<< created
87
+ parent.
88
+ else
89
+
90
+ end
91
+ end
92
+ end
93
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsonapi_compliable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.7
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lee Richmond
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2017-03-13 00:00:00.000000000 Z
12
+ date: 2017-04-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: jsonapi-serializable
@@ -149,7 +149,7 @@ files:
149
149
  - lib/jsonapi_compliable/adapters/active_record_sideloading.rb
150
150
  - lib/jsonapi_compliable/adapters/null.rb
151
151
  - lib/jsonapi_compliable/base.rb
152
- - lib/jsonapi_compliable/deserializable.rb
152
+ - lib/jsonapi_compliable/deserializer.rb
153
153
  - lib/jsonapi_compliable/errors.rb
154
154
  - lib/jsonapi_compliable/extensions/boolean_attribute.rb
155
155
  - lib/jsonapi_compliable/extensions/extra_attribute.rb
@@ -170,8 +170,12 @@ files:
170
170
  - lib/jsonapi_compliable/util/field_params.rb
171
171
  - lib/jsonapi_compliable/util/hash.rb
172
172
  - lib/jsonapi_compliable/util/include_params.rb
173
+ - lib/jsonapi_compliable/util/persistence.rb
174
+ - lib/jsonapi_compliable/util/relationship_payload.rb
173
175
  - lib/jsonapi_compliable/util/render_options.rb
176
+ - lib/jsonapi_compliable/util/validation_response.rb
174
177
  - lib/jsonapi_compliable/version.rb
178
+ - lib/jsonapi_compliable/write.rb
175
179
  homepage:
176
180
  licenses:
177
181
  - MIT
@@ -1,127 +0,0 @@
1
- # This will convert JSONAPI-compatible POST/PUT payloads
2
- # into something Rails better understands. Example:
3
- #
4
- # {
5
- # "data": {
6
- # "type": "articles",
7
- # "attributes": { "title": "the first article" },
8
- # "relationships": {
9
- # "tags": {
10
- # "data": [{
11
- # "type": "tags",
12
- # "attributes": { "name": "One" }
13
- # }, {
14
- # "type": "tags",
15
- # "attributes": { "name": "Two" }
16
- # }]
17
- # }
18
- # }
19
- # }
20
- # }
21
- #
22
- # Into:
23
- #
24
- # {
25
- # article: {
26
- # title: 'the first article',
27
- # tags_attributes: [
28
- # { name: 'One' },
29
- # { name: 'Two' },
30
- # ]
31
- # }
32
- # }
33
- #
34
- # Why we don't use AMS deserialization - AMS will:
35
- # * not support relationship data
36
- # * override foreign key incorrectly, ie
37
- # post_id incorrectly becomes nil if post relation is nil,
38
- # even if it is in the attributes payload
39
- #
40
- # Usage:
41
- #
42
- # In controller:
43
- #
44
- # before_action :deserialize_jsonapi!, only: [:my_action]
45
-
46
- module JsonapiCompliable
47
- module Deserializable
48
- extend ActiveSupport::Concern
49
-
50
- included do
51
- attr_accessor :raw_params
52
- end
53
-
54
- class Deserialization
55
- def initialize(params, namespace: true)
56
- @params = params
57
- @namespace = namespace
58
- end
59
-
60
- def deserialize
61
- hash = attributes
62
- hash = hash.merge(relationships)
63
- hash = @namespace ? { parsed_type => hash } : hash
64
- hash.reverse_merge(@params.except(:data)).deep_symbolize_keys
65
- end
66
-
67
- private
68
-
69
- def parsed_type
70
- @params[:data][:type].underscore.singularize.to_sym
71
- end
72
-
73
- def attributes
74
- attrs = {}
75
- attrs[:id] = @params[:data].try(:[], :id) if @params[:data].try(:[], :id)
76
- attrs.merge!(@params[:data].try(:[], :attributes) || {})
77
- attrs
78
- end
79
-
80
- def relationships
81
- return {} if @params[:data].try(:[], :relationships).blank?
82
-
83
- {}.tap do |hash|
84
- @params[:data][:relationships].each_pair do |relationship_name, payload|
85
- parsed_relation = parse_relation(payload)
86
-
87
- if parsed_relation.present?
88
- hash["#{relationship_name}_attributes".to_sym] = parsed_relation
89
- end
90
- end
91
- end
92
- end
93
-
94
- def parse_relation(payload)
95
- if payload[:data].is_a?(Array)
96
- parse_has_many(payload[:data])
97
- else
98
- parse_belongs_to(payload)
99
- end
100
- end
101
-
102
- def parse_belongs_to(payload)
103
- self.class.new(payload, namespace: false).deserialize
104
- end
105
-
106
- def parse_has_many(payloads)
107
- payloads.map do |payload|
108
- payload = { data: payload }
109
- self.class.new(payload, namespace: false).deserialize
110
- end.compact
111
- end
112
- end
113
-
114
- def deserialize_jsonapi!
115
- self.raw_params = Util::Hash.deep_dup(self.params)
116
-
117
- if defined?(::Rails) && (is_a?(ActionController::Base) || (defined?(ActionController::API) && is_a?(ActionController::API)))
118
- hash = params.to_unsafe_h
119
- hash = hash.with_indifferent_access if ::Rails::VERSION::MAJOR == 4
120
- deserialized = Deserialization.new(hash).deserialize
121
- self.params = ActionController::Parameters.new(deserialized)
122
- else
123
- self.params = Deserialization.new(params).deserialize
124
- end
125
- end
126
- end
127
- end