jsonapi_compliable 0.5.7 → 0.6.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: 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