jsonapionify 0.10.2 → 0.11.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,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ODc3NmE4NzVhYTc0N2NlZTE4ZDNiODAxOThmMzFmMmM1NjE4MTBhZg==
4
+ Y2Y5ZTgxYWU1NTJhOTMyOGY3OWQyMDQ1Y2RlZTZmZmFjYzQwOTRiZA==
5
5
  data.tar.gz: !binary |-
6
- MGVlNWMyY2Q0NTFhMmRmNjc5YTAzNTA3NWFkODEzZTc3OTRhZGIzZg==
6
+ Yzg1ZmQyZmViOWZhYjM1MjQxOGUwZDc3YjdjMzJkYzdmZWUzMjJlZg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- NDBjN2U5NDBjMWQ3MzI0MzUyOTU0YWEzZDYxM2FhNmMzMDAzMGZkZThkNDEy
10
- ZTBhZThkYzMwNTIzY2JlOGQzNTU0N2MyNjllMjA1OGRhYTA5ZmFhYjVjYjQ3
11
- ZDI2MTRkYmZkMWZhNjJlYzAzMDUyZjQzODJmZmM1YWVlMzk1ZjI=
9
+ NTZiMTc4NjZhZWU5MDk4Yjg5MzhlNmNkMTU0MThjNTRmMDY4YjlmY2M5ZWNj
10
+ Nzc1Njk0MTYzMTAxOTM4ODQ4NTVhZjljY2YyYTI3NTRiM2U4MjM5YWEzMThi
11
+ OThkYmMyODc0MDA3YTcwODllZTVlZTZkZTE4OTdkOTRjNGI4ZTg=
12
12
  data.tar.gz: !binary |-
13
- NmI2ODFmYjU3ZjRhOWQ3ZjljZDQ2NTI5NDA1MzM4NGU5ZTIxNDVjZDY3Nzll
14
- NGFkZmMxNzY3NzFjYmRlNTVmMDAyYjA5ZTJjMWE4MzQyNTc2NWZlYzkyZjg2
15
- MGNlZTkyNGI3MDk1YzJjMTBjMmQ2YzcxNTdiY2I5Mjc3MDY4YTM=
13
+ MGYxNzUyZDcwMjdkMmY5MDUyMmQwZThjNGY2MzRiODRlZjUwMjQxZWNkYWZm
14
+ Yzc2OGZhNDEzNTU1NTg0MGFmNGZjZjg4Yzc3MDI1ZWJlYWFiOWI2YWE4OTNh
15
+ YzllY2E3YmEzYTVjNmFhYWI3YjQxZjRiMDVmNDA2NjdiYzg1OWM=
data/jsonapionify.gemspec CHANGED
@@ -36,6 +36,7 @@ Gem::Specification.new do |spec|
36
36
  spec.add_dependency 'mime-types'
37
37
 
38
38
  spec.add_development_dependency 'pry'
39
+ spec.add_development_dependency 'pry-byebug'
39
40
  spec.add_development_dependency 'rocco'
40
41
  spec.add_development_dependency 'bundler', '~> 1.10'
41
42
  spec.add_development_dependency 'rake', '~> 10.0'
@@ -20,17 +20,15 @@ module JSONAPIonify::Api
20
20
  when :resource
21
21
  {
22
22
  'data' => resource.build_resource(
23
- context,
24
- resource.example_instance_for_action(name, context),
25
- relationships: false,
26
- links: false,
27
- fields: resource.fields_for_action(name, context)
23
+ context: context,
24
+ instance: resource.example_instance_for_action(name, context),
25
+ links: false
28
26
  ).as_json
29
27
  }.to_json
30
28
  when :resource_identifier
31
29
  {
32
30
  'data' => resource.build_resource_identifier(
33
- resource.example_instance_for_action(name, context)
31
+ instance: resource.example_instance_for_action(name, context)
34
32
  ).as_json
35
33
  }.to_json
36
34
  when Proc
@@ -7,7 +7,7 @@ module JSONAPIonify::Api
7
7
 
8
8
  include Documentation
9
9
  using UnstrictProc
10
- attr_reader :name, :type, :description, :read, :write, :required, :block
10
+ attr_reader :name, :type, :description, :read, :write, :required, :hidden, :block
11
11
 
12
12
  def initialize(
13
13
  name,
@@ -17,6 +17,7 @@ module JSONAPIonify::Api
17
17
  write: true,
18
18
  required: false,
19
19
  example: nil,
20
+ hidden: false,
20
21
  &block
21
22
  )
22
23
  unless type.is_a? JSONAPIonify::Types::BaseType
@@ -33,6 +34,7 @@ module JSONAPIonify::Api
33
34
  @block = block&.freeze
34
35
  @writeable_actions = write
35
36
  @readable_actions = read
37
+ @hidden = !!hidden && (hidden == true || Array.wrap(hidden))
36
38
 
37
39
  freeze
38
40
  end
@@ -42,6 +44,11 @@ module JSONAPIonify::Api
42
44
  self.name == other.name
43
45
  end
44
46
 
47
+ def hidden_for_action?(action_name)
48
+ return false if hidden == false
49
+ Array.wrap(hidden).any? { |h| h == true || h.to_s == action_name.to_s }
50
+ end
51
+
45
52
  def supports_read_for_action?(action_name, context)
46
53
  case (setting = @readable_actions)
47
54
  when TrueClass, FalseClass
@@ -63,15 +63,21 @@ module JSONAPIonify::Api
63
63
  end
64
64
  end
65
65
 
66
- attr_reader :owner, :class_proc, :name, :resolve
66
+ attr_reader :owner, :class_proc, :name, :resolve, :hidden
67
67
 
68
- def initialize(owner, name, resource: nil, includable: false, resolve: proc { |name, owner| owner.send(name) }, &block)
68
+ def initialize(owner, name, resource: nil, includable: false, hidden: :index, resolve: proc { |n, o| o.send(n) }, &block)
69
69
  @class_proc = block || proc {}
70
70
  @owner = owner
71
71
  @name = name
72
72
  @includable = includable
73
73
  @resource = resource || name
74
74
  @resolve = resolve
75
+ @hidden = !!hidden && (hidden == true || Array.wrap(hidden))
76
+ end
77
+
78
+ def hidden_for_action?(action_name)
79
+ return false if hidden == false
80
+ Array.wrap(hidden).any? { |h| h == true || h.to_s == action_name.to_s }
75
81
  end
76
82
 
77
83
  def resource_class
@@ -82,9 +88,9 @@ module JSONAPIonify::Api
82
88
  rel
83
89
  end
84
90
 
85
- rel.class_prepends.each { |prepend| class_eval &prepend }
91
+ rel.class_prepends.each { |prepend| class_eval(&prepend) }
86
92
  class_eval(&rel.class_proc)
87
- rel.class_appends.each { |append| class_eval &append }
93
+ rel.class_appends.each { |append| class_eval(&append) }
88
94
  end
89
95
  end
90
96
  end
@@ -16,7 +16,7 @@ module JSONAPIonify::Api
16
16
  prepend: 'relationships'
17
17
  }
18
18
  define_action(:show, 'GET', **options, &block).response status: 200 do |context|
19
- context.response_object[:data] = build_identifier_collection(context.collection)
19
+ context.response_object[:data] = build_resource_identifier_collection(collection: context.collection)
20
20
  context.response_object.to_json
21
21
  end
22
22
  end
@@ -30,7 +30,7 @@ module JSONAPIonify::Api
30
30
  example_input: :resource_identifier
31
31
  }
32
32
  define_action(:replace, 'PATCH', **options, &block).response status: 200 do |context|
33
- context.response_object[:data] = build_identifier_collection(context.collection)
33
+ context.response_object[:data] = build_resource_identifier_collection(collection: context.collection)
34
34
  context.response_object.to_json
35
35
  end
36
36
  end
@@ -44,7 +44,7 @@ module JSONAPIonify::Api
44
44
  example_input: :resource_identifier
45
45
  }
46
46
  define_action(:add, 'POST', **options, &block).response status: 200 do |context|
47
- context.response_object[:data] = build_identifier_collection(context.collection)
47
+ context.response_object[:data] = build_resource_identifier_collection(collection: context.collection)
48
48
  context.response_object.to_json
49
49
  end
50
50
  end
@@ -59,7 +59,7 @@ module JSONAPIonify::Api
59
59
  }
60
60
  options[:prepend] = 'relationships'
61
61
  define_action(:remove, 'DELETE', **options, &block).response status: 200 do |context|
62
- context.response_object[:data] = build_identifier_collection(context.collection)
62
+ context.response_object[:data] = build_resource_identifier_collection(collection: context.collection)
63
63
  context.response_object.to_json
64
64
  end
65
65
  end
@@ -17,7 +17,7 @@ module JSONAPIonify::Api
17
17
  prepend: 'relationships'
18
18
  }
19
19
  define_action(:show, 'GET', **options, &block).response status: 200 do |context|
20
- context.response_object[:data] = build_resource_identifier(context.instance)
20
+ context.response_object[:data] = build_resource_identifier(instance: context.instance)
21
21
  context.response_object.to_json
22
22
  end
23
23
  end
@@ -30,7 +30,7 @@ module JSONAPIonify::Api
30
30
  prepend: 'relationships'
31
31
  }
32
32
  define_action(:replace, 'PATCH', **options, &block).response status: 200 do |context|
33
- context.response_object[:data] = build_resource_identifier(context.instance)
33
+ context.response_object[:data] = build_resource_identifier(instance: context.instance)
34
34
  context.response_object.to_json
35
35
  end
36
36
  end
@@ -1,159 +1,48 @@
1
1
  module JSONAPIonify::Api
2
2
  module Resource::Builders
3
3
  extend ActiveSupport::Concern
4
+ extend JSONAPIonify::Autoload
5
+
6
+ autoload_all
7
+
4
8
  FALSEY_STRINGS = JSONAPIonify::FALSEY_STRINGS
5
9
  TRUTHY_STRINGS = JSONAPIonify::TRUTHY_STRINGS
6
- Structure = JSONAPIonify::Structure
7
10
 
8
11
  module ClassMethods
12
+ include JSONAPIonify::Structure
9
13
 
10
- def build_resource(
11
- context,
12
- instance,
13
- fields:,
14
- relationships: true,
15
- links: true,
16
- include_cursor: false, &block
17
- )
18
- example_id = generate_id
19
- include_rel_param = context.params['include-relationships']
20
- relationships = false if FALSEY_STRINGS.include?(include_rel_param)
21
- return nil unless instance
22
- resource_url = build_url(context, instance)
23
- id = build_id(instance)
24
- Structure::Objects::Resource.new.tap do |resource|
25
- resource[:id] = id
26
- resource[:type] = type
27
-
28
- resource[:attributes] = fields[type.to_sym].each_with_object(
29
- Structure::Objects::Attributes.new
30
- ) do |member, attributes|
31
- attribute = self.attributes.find { |a| a.name == member.to_sym }
32
- unless attribute.supports_read_for_action?(context.action_name, context)
33
- error_block =
34
- context.resource.class.error_definitions[:internal_server_error]
35
- context.errors.evaluate(
36
- name,
37
- error_block: error_block,
38
- runtime_block: proc {}
39
- )
40
- end
41
- attributes[member.to_sym] = attribute.resolve(
42
- instance, context, example_id: example_id
43
- )
44
- end
45
-
46
- resource[:links] = Structure::Objects::Links.new(
47
- self: resource_url
48
- ) if links
49
-
50
- resource[:meta] = {
51
- cursor: build_cursor_from_instance(context, instance)
52
- } if include_cursor
53
-
54
- resource[:relationships] = relationship_definitions.each_with_object(
55
- Structure::Maps::Relationships.new
56
- ) do |rel, hash|
57
- hash[rel.name] = build_relationship(context, instance, rel.name)
58
- end if relationships || context.includes.present?
59
-
60
- block.call(resource, instance) if block
61
- end
14
+ def build_resource(**options, &block)
15
+ ResourceBuilder.build(self, **options)
62
16
  end
63
17
 
64
- def build_resource_identifier(instance)
65
- Structure::Objects::ResourceIdentifier.new(
66
- id: build_id(instance),
67
- type: type.to_s
68
- )
69
- end
70
-
71
- def build_collection(
72
- context,
73
- collection,
74
- fields:,
75
- include_cursors: false,
76
- &block
77
- )
78
- include_rel_param = context.params['include-relationships']
79
- relationships = TRUTHY_STRINGS.include? include_rel_param
18
+ def build_resource_collection(context:, collection:, include_cursors: false, &block)
80
19
  collection.each_with_object(
81
- Structure::Collections::Resources.new
20
+ Collections::Resources.new
82
21
  ) do |instance, resources|
83
- resources << build_resource(
84
- context,
85
- instance,
86
- fields: fields,
87
- relationships: relationships,
88
- include_cursor: include_cursors,
89
- &block
90
- )
22
+ resources << build_resource(context: context, instance: instance, include_cursor: include_cursors, &block)
91
23
  end
92
24
  end
93
25
 
94
- def build_cursor_from_instance(context, instance)
95
- sort_string = context.params['sort']
96
- sort_fields = sort_fields_from_sort_string(sort_string)
97
- attrs_with_values = sort_fields.each_with_object({}) do |field, hash|
98
- hash[field.name] = instance.send(field.name)
99
- end
100
- Base64.urlsafe_encode64(JSON.dump(
101
- {
102
- t: type,
103
- s: sort_string,
104
- a: attrs_with_values
105
- }
106
- ))
26
+ def build_resource_identifier(instance:)
27
+ ResourceIdentifierBuilder.build(self, instance: instance)
107
28
  end
108
29
 
109
- def build_identifier_collection(collection)
30
+ def build_resource_identifier_collection(collection:)
110
31
  collection.each_with_object(
111
- JSONAPIonify::Structure::Collections::ResourceIdentifiers.new
32
+ Collections::ResourceIdentifiers.new
112
33
  ) do |instance, resource_identifiers|
113
- resource_identifiers << build_resource_identifier(instance)
34
+ resource_identifiers << build_resource_identifier(instance: instance)
114
35
  end
115
36
  end
116
37
 
117
- def build_relationship(context, instance, name, links: true, data: false)
118
- resource_url = build_url(context, instance)
119
- relationship = self.relationship(name)
120
- JSONAPIonify::Structure::Objects::Relationship.new.tap do |rel|
121
- rel[:links] = relationship.build_links(resource_url) if links
122
- if data || context.includes.present?
123
- rel[:data] =
124
- if relationship.rel.is_a? Relationship::Many
125
- instance.send(name).map do |child|
126
- relationship.build_resource_identifier(child)
127
- end
128
- elsif relationship.rel.is_a? Relationship::One
129
- value = instance.send(name)
130
- relationship.build_resource_identifier value if value
131
- end
132
- end
133
- end
38
+ def build_cursor(**options)
39
+ CursorBuilder.build(self, **options)
134
40
  end
135
41
 
136
- def build_url(context, instance = nil)
137
- URI.parse(context.request.root_url).tap do |uri|
138
- uri.path =
139
- if instance
140
- File.join(uri.path, type, build_id(instance))
141
- else
142
- File.join(context.request.root_url, type)
143
- end
144
- sticky_params = self.sticky_params(context.params)
145
- uri.query = sticky_params.to_param if sticky_params.present?
146
- end.to_s
147
- end
148
-
149
- def build_id(instance)
150
- instance.send(id_attribute).to_s
151
- end
152
42
  end
153
43
 
44
+ delegated_methods = ClassMethods.instance_methods - instance_methods
154
45
  included do
155
- delegated_methods = ClassMethods.instance_methods -
156
- JSONAPIonify::Api::Resource::Builders.instance_methods
157
46
  delegate(*delegated_methods, to: :class)
158
47
  end
159
48
 
@@ -0,0 +1,29 @@
1
+ module JSONAPIonify::Api
2
+ module Resource::Builders
3
+ class AttributesBuilder < FieldsBuilder
4
+
5
+ delegate :attributes, to: :resource, prefix: true
6
+
7
+ private
8
+
9
+ def build_default
10
+ resource_attributes.each_with_object(Objects::Attributes.new) do |attribute, attrs|
11
+ if attribute.supports_read_for_action?(action_name, context) && !attribute.hidden_for_action?(action_name)
12
+ attrs[attribute.name] = attribute.resolve(instance, context, example_id: example_id)
13
+ end
14
+ end
15
+ end
16
+
17
+ def build_sparce
18
+ resource_fields.each_with_object(Objects::Attributes.new) do |field, attrs|
19
+ field = field.to_sym
20
+ attribute = resource_attributes.find { |attr| attr.name == field }
21
+ if attribute&.supports_read_for_action?(action_name, context)
22
+ attrs[field] = attribute.resolve(instance, context, example_id: example_id)
23
+ end
24
+ end
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ module JSONAPIonify::Api
2
+ module Resource::Builders
3
+ class BaseBuilder
4
+ include JSONAPIonify::Structure
5
+
6
+ def self.build(*args, &block)
7
+ new(*args, &block).build
8
+ end
9
+
10
+ attr_reader :resource
11
+
12
+ def initialize(resource)
13
+ @resource = resource
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,33 @@
1
+ module JSONAPIonify::Api
2
+ module Resource::Builders
3
+ class CursorBuilder < BaseBuilder
4
+
5
+ attr_reader :context, :instance
6
+ delegate :type, to: :resource, prefix: true
7
+ delegate :sort_fields_from_sort_string, to: :resource
8
+ delegate :request, :params, to: :context
9
+
10
+ def initialize(resource, instance:, context:)
11
+ super(resource)
12
+ @instance = instance
13
+ @context = context
14
+ end
15
+
16
+ def build
17
+ sort_string = params['sort']
18
+ sort_fields = sort_fields_from_sort_string(sort_string)
19
+ attrs_with_values = sort_fields.each_with_object({}) do |field, hash|
20
+ hash[field.name] = instance.send(field.name)
21
+ end
22
+ Base64.urlsafe_encode64(JSON.dump(
23
+ {
24
+ t: resource_type,
25
+ s: sort_string,
26
+ a: attrs_with_values
27
+ }
28
+ ))
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,26 @@
1
+ module JSONAPIonify::Api
2
+ module Resource::Builders
3
+ class FieldsBuilder < BaseBuilder
4
+
5
+ attr_reader :context, :instance, :example_id
6
+ delegate :action_name, :fields, to: :context
7
+ delegate :type, to: :resource, prefix: true
8
+
9
+ def initialize(resource, instance:, context:, example_id:)
10
+ super(resource)
11
+ @instance = instance
12
+ @context = context
13
+ @example_id = example_id
14
+ end
15
+
16
+ def resource_fields
17
+ fields[resource_type] || {}
18
+ end
19
+
20
+ def build
21
+ fields.nil? ? build_default : build_sparce
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ module JSONAPIonify::Api
2
+ module Resource::Builders
3
+ module IdentityHelper
4
+ def build_url
5
+ URI.parse(request.root_url).tap do |uri|
6
+ uri.path = File.join uri.path, resource_type, build_id
7
+ sticky_params = resource.sticky_params(context.params)
8
+ uri.query = sticky_params.to_param if sticky_params.present?
9
+ end.to_s
10
+ end
11
+
12
+ def build_id
13
+ instance.send(resource.id_attribute).to_s
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,49 @@
1
+ module JSONAPIonify::Api
2
+ module Resource::Builders
3
+ class RelationshipBuilder < BaseBuilder
4
+ include IdentityHelper
5
+ delegate :type, to: :resource, prefix: true
6
+ delegate :request, :includes, to: :context
7
+
8
+ attr_reader :relationship, :related_resource, :context, :instance
9
+
10
+ def initialize(resource, relationship:, context:, instance:)
11
+ super(resource)
12
+ @relationship = relationship
13
+ @context = context
14
+ @instance = instance
15
+ @related_resource = resource.relationship(relationship.name)
16
+ end
17
+
18
+ def build
19
+ Objects::Relationship.new.tap do |rel|
20
+ rel[:links] = related_resource.build_links(build_url)
21
+ rel[:data] = build_data if includes.present?
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def build_data
28
+ case relationship
29
+ when Relationship::Many
30
+ resolution.map do |child|
31
+ relationship.build_resource_identifier(child)
32
+ end
33
+ when Relationship::One
34
+ relationship.build_resource_identifier resolution
35
+ end
36
+ end
37
+
38
+ def resolution
39
+ instance.instance_exec(
40
+ relationship.name,
41
+ instance,
42
+ context,
43
+ &relationship.resolve
44
+ )
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,36 @@
1
+ module JSONAPIonify::Api
2
+ module Resource::Builders
3
+ class RelationshipsBuilder < FieldsBuilder
4
+
5
+ delegate :relationships, to: :resource, prefix: true
6
+
7
+ private
8
+
9
+ def build_default
10
+ resource_relationships.each_with_object(Objects::Relationships.new) do |relationship, attrs|
11
+ unless relationship.hidden_for_action?(action_name)
12
+ attrs[relationship.name] = build_relationship(relationship)
13
+ end
14
+ end
15
+ end
16
+
17
+ def build_sparce
18
+ resource_fields.each_with_object(Objects::Relationships.new) do |field, attrs|
19
+ field = field.to_sym
20
+ relationship = resource_relationships.find { |rel| rel.name == field }
21
+ attrs[field] = build_relationship(relationship) if relationship
22
+ end
23
+ end
24
+
25
+ def build_relationship(relationship)
26
+ RelationshipBuilder.build(
27
+ resource,
28
+ relationship: relationship,
29
+ context: context,
30
+ instance: instance
31
+ )
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,66 @@
1
+ module JSONAPIonify::Api
2
+ module Resource::Builders
3
+ class ResourceBuilder < ResourceIdentifierBuilder
4
+ delegate :attributes, :relationships, :type, :relationship, to: :resource, prefix: true
5
+ delegate :params, :includes, :fields, :action_name, :request, to: :context
6
+
7
+ attr_reader :context, :links, :include_cursor, :block
8
+
9
+ def initialize(resource, context:, links: true, include_cursor: false, **opts, &block)
10
+ super(resource, **opts)
11
+ @context = context
12
+ @links = links
13
+ @include_cursor = include_cursor
14
+ @block = block
15
+ end
16
+
17
+ def build
18
+ return nil unless instance
19
+ Objects::Resource.new.tap do |resource|
20
+ resource[:type] = resource_type
21
+ (id = build_id) && resource[:id] = id
22
+ (attributes = build_attributes).present? && resource[:attributes] = attributes
23
+ (relationships = build_relationships).present? && resource[:relationships] = relationships
24
+ (links = build_links).present? && resource[:links] = links
25
+ (meta = build_meta).present? && resource[:meta] = meta
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def build_attributes
32
+ AttributesBuilder.build(
33
+ resource,
34
+ instance: instance,
35
+ context: context,
36
+ example_id: example_id
37
+ )
38
+ end
39
+
40
+ def build_relationships
41
+ RelationshipsBuilder.build(
42
+ resource,
43
+ instance: instance,
44
+ context: context,
45
+ example_id: example_id
46
+ )
47
+ end
48
+
49
+ def build_links
50
+ return unless links
51
+ Objects::Links.new(self: build_url)
52
+ end
53
+
54
+ def build_meta
55
+ Objects::Meta.new.tap do |meta|
56
+ (cursor = build_cursor).present? && meta[:cursor] = cursor
57
+ end
58
+ end
59
+
60
+ def build_cursor
61
+ return unless include_cursor
62
+ CursorBuilder.build(resource, instance: instance, context: context)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,25 @@
1
+ module JSONAPIonify::Api
2
+ module Resource::Builders
3
+ class ResourceIdentifierBuilder < BaseBuilder
4
+ include IdentityHelper
5
+
6
+ attr_reader :example_id, :instance
7
+ delegate :type, to: :resource, prefix: true
8
+
9
+ def initialize(resource, instance:)
10
+ super(resource)
11
+ @instance = instance
12
+ @example_id = resource.generate_id
13
+ end
14
+
15
+ def build
16
+ return nil unless instance
17
+ Objects::ResourceIdentifier.new.tap do |resource|
18
+ resource[:type] = resource_type
19
+ (id = build_id) && resource[:id] = id
20
+ end
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -5,108 +5,82 @@ module JSONAPIonify::Api
5
5
  module Resource::Definitions::Actions
6
6
  ActionNotFound = Class.new StandardError
7
7
 
8
+ INSTANCE_RESPONSE = proc do |context|
9
+ builder =
10
+ context.respond_to?(:builder) ? context.builder : nil
11
+ context.response_object[:data] =
12
+ build_resource(
13
+ context: context,
14
+ instance: context.instance,
15
+ &builder
16
+ )
17
+ context.response_object.to_json
18
+ end
19
+
20
+ COLLECTION_RESPONSE = proc do |context|
21
+ builder = context.respond_to?(:builder) ? context.builder : nil
22
+ context.response_object[:data] = build_resource_collection(
23
+ context: context,
24
+ collection: context.response_collection,
25
+ include_cursors: (context.links.keys & [:first, :last, :next, :prev]).present?,
26
+ &builder
27
+ )
28
+ context.response_object.to_json
29
+ end
30
+
8
31
  def self.extended(klass)
9
32
  klass.class_eval do
10
33
  extend JSONAPIonify::InheritedAttributes
11
34
  include JSONAPIonify::Callbacks
12
35
  define_callbacks(
13
- :request, :exception, :response,
14
- :list, :commit_list,
15
- :create, :commit_create,
16
- :read, :commit_read,
17
- :update, :commit_update,
18
- :delete, :commit_delete,
19
- :show, :commit_show,
20
- :add, :commit_add,
21
- :remove, :commit_remove,
36
+ :request,
37
+ :exception,
38
+ :response,
39
+ :list, :commit_list,
40
+ :create, :commit_create,
41
+ :read, :commit_read,
42
+ :update, :commit_update,
43
+ :delete, :commit_delete,
44
+ :show, :commit_show,
45
+ :add, :commit_add,
46
+ :remove, :commit_remove,
22
47
  :replace, :commit_replace
23
48
  )
24
49
  inherited_array_attribute :action_definitions
25
50
  end
26
51
  end
27
52
 
28
- def list(content_type: nil, only_associated: false, callbacks: true, &block)
29
- options = {
30
- content_type: content_type,
31
- only_associated: only_associated,
32
- callbacks: callbacks,
33
- cacheable: true
34
- }
35
- define_action(:list, 'GET', **options, &block).tap do |action|
36
- action.response status: 200 do |context|
37
- builder = context.respond_to?(:builder) ? context.builder : nil
38
- context.response_object[:data] = build_collection(
39
- context,
40
- context.response_collection,
41
- fields: context.fields,
42
- include_cursors: (context.links.keys & [:first, :last, :next, :prev]).present?,
43
- &builder
44
- )
45
- context.response_object.to_json
46
- end
53
+ def list(**options, &block)
54
+ define_action(:list, 'GET', **options, cacheable: true, &block).tap do |action|
55
+ action.response(status: 200, &COLLECTION_RESPONSE)
47
56
  end
48
57
  end
49
58
 
50
- def create(content_type: nil, only_associated: false, callbacks: true, &block)
51
- options = {
52
- content_type: content_type,
53
- only_associated: only_associated,
54
- callbacks: callbacks,
55
- cacheable: false,
56
- example_input: :resource
57
- }
58
- define_action(:create, 'POST', **options, &block).tap do |action|
59
- action.response status: 201 do |context|
60
- builder = context.respond_to?(:builder) ? context.builder : nil
61
- context.response_object[:data] = build_resource(context, context.instance, fields: context.fields, &builder)
62
- response_headers['Location'] = build_url(context, context.instance)
63
- context.response_object.to_json
59
+ def create(**options, &block)
60
+ define_action(:create, 'POST', '', **options, cacheable: false, example_input: :resource, &block).tap do |action|
61
+ action.response(status: 201) do |context|
62
+ instance_exec(context, &INSTANCE_RESPONSE).tap do
63
+ response_headers['Location'] = context.response_object[:data][:links][:self]
64
+ end
64
65
  end
65
66
  end
66
67
  end
67
68
 
68
- def read(content_type: nil, only_associated: false, callbacks: true, &block)
69
- options = {
70
- content_type: content_type,
71
- only_associated: only_associated,
72
- callbacks: callbacks,
73
- cacheable: true
74
- }
75
- define_action(:read, 'GET', '/:id', **options, &block).tap do |action|
76
- action.response status: 200 do |context|
77
- builder = context.respond_to?(:builder) ? context.builder : nil
78
- context.response_object[:data] = build_resource(context, context.instance, fields: context.fields, &builder)
79
- context.response_object.to_json
80
- end
69
+ def read(**options, &block)
70
+ define_action(:read, 'GET', '/:id', **options, cacheable: true, &block).tap do |action|
71
+ action.response(status: 200, &INSTANCE_RESPONSE)
81
72
  end
82
73
  end
83
74
 
84
- def update(content_type: nil, only_associated: false, callbacks: true, &block)
85
- options = {
86
- content_type: content_type,
87
- only_associated: only_associated,
88
- callbacks: callbacks,
89
- cacheable: false,
90
- example_input: :resource
91
- }
92
- define_action(:update, 'PATCH', '/:id', **options, &block).tap do |action|
93
- action.response status: 200 do |context|
94
- builder = context.respond_to?(:builder) ? context.builder : nil
95
- context.response_object[:data] = build_resource(context, context.instance, fields: context.fields, &builder)
96
- context.response_object.to_json
97
- end
75
+ def update(**options, &block)
76
+ define_action(:update, 'PATCH', '/:id', **options, cacheable: false, example_input: :resource, &block).tap do |action|
77
+ action.response(status: 200, &INSTANCE_RESPONSE)
98
78
  end
99
79
  end
100
80
 
101
- def delete(content_type: nil, only_associated: false, callbacks: true, &block)
102
- options = {
103
- content_type: content_type,
104
- only_associated: only_associated,
105
- callbacks: callbacks,
106
- cacheable: false
107
- }
108
- define_action(:delete, 'DELETE', '/:id', **options, &block).tap do |action|
109
- action.response status: 204
81
+ def delete(**options, &block)
82
+ define_action(:delete, 'DELETE', '/:id', **options, cacheable: false, &block).tap do |action|
83
+ action.response(status: 204)
110
84
  end
111
85
  end
112
86
 
@@ -120,17 +94,12 @@ module JSONAPIonify::Api
120
94
  end
121
95
  end
122
96
 
123
- def after(*action_names, &block)
124
- return after_request &block if action_names.blank?
125
- action_names.each do |action_name|
126
- send("after_#{action_name}", &block)
127
- end
128
- end
129
-
130
- def before(*action_names, &block)
131
- return before_request &block if action_names.blank?
132
- action_names.each do |action_name|
133
- send("before_#{action_name}", &block)
97
+ %i{before after}.each do |cb|
98
+ define_method(cb) do |*action_names, &block|
99
+ return send(:"#{cb}_request", &block) if action_names.blank?
100
+ action_names.each do |action_name|
101
+ send("#{cb}_#{action_name}", &block)
102
+ end
134
103
  end
135
104
  end
136
105
 
@@ -9,31 +9,9 @@ module JSONAPIonify::Api
9
9
  delegate :id_attribute, :attributes, to: :class
10
10
 
11
11
  context(:fields, readonly: true) do |context|
12
- should_error = false
13
- input_fields = context.request.params['fields'] || {}
14
- actionable_fields = self.class.fields_for_action(context.action_name, context)
15
- input_fields.each_with_object(
16
- actionable_fields
17
- ) do |(type, fields), field_map|
18
- type_sym = type.to_sym
19
- field_symbols = fields.to_s.split(',').map(&:to_sym)
20
- field_map[type_sym] =
21
- field_symbols.each_with_object([]) do |field, field_list|
22
- type_attributes = self.class.api.resource(type_sym).attributes
23
- attribute = type_attributes.find do |attribute|
24
- attribute.name == field &&
25
- attribute.supports_read_for_action?(context.action_name, context)
26
- end
27
- if attribute
28
- field_list << attribute.name
29
- else
30
- error(:field_not_permitted, type, field)
31
- should_error = true
32
- end
33
- end
34
- end.tap do
35
- halt if should_error
36
- end
12
+ context.params['fields']&.map do |type, fields|
13
+ [type, fields.split(',')]
14
+ end&.to_h
37
15
  end
38
16
  end
39
17
  end
@@ -57,10 +35,6 @@ module JSONAPIonify::Api
57
35
  attributes.delete_if { |attr| attr.name == name.to_sym }
58
36
  end
59
37
 
60
- def fields
61
- attributes.select(&:read?).map(&:name)
62
- end
63
-
64
38
  def builder(&block)
65
39
  context :builder, readonly: true, persisted: true do |context|
66
40
  proc do |resource, instance|
@@ -69,19 +43,5 @@ module JSONAPIonify::Api
69
43
  end
70
44
  end
71
45
 
72
- def field_valid?(name)
73
- fields.include? name.to_sym
74
- end
75
-
76
- def fields_for_action(action, context)
77
- api.fields.each_with_object({}) do |(type, attrs), fields|
78
- fields[type] = attrs.select do |attr|
79
- api.resource(type).attributes.find do |type_attr|
80
- type_attr.name == attr
81
- end.supports_read_for_action? action, context
82
- end
83
- end
84
- end
85
-
86
46
  end
87
47
  end
@@ -51,8 +51,8 @@ module JSONAPIonify::Api
51
51
 
52
52
  links.first first: size
53
53
  links.last last: size
54
- links.prev before: build_cursor_from_instance(context.request, slice.first), last: size unless !slice.first || slice.first == collection.first
55
- links.next after: build_cursor_from_instance(context.request, slice.last), first: size unless !slice.last || slice.last == collection.last
54
+ links.prev before: build_cursor(context: context, instance: slice.first), last: size unless !slice.first || slice.first == collection.first
55
+ links.next after: build_cursor(context: context, instance: slice.last), first: size unless !slice.last || slice.last == collection.last
56
56
 
57
57
  slice
58
58
  end
@@ -82,8 +82,8 @@ module JSONAPIonify::Api
82
82
 
83
83
  links.first first: size
84
84
  links.last last: size
85
- links.prev before: build_cursor_from_instance(context.request, slice.first), last: size unless !slice.first || slice.first == collection.first
86
- links.next after: build_cursor_from_instance(context.request, slice.last), first: size unless !slice.last || slice.last == collection.last
85
+ links.prev before: build_cursor(context: context, instance: slice.first), last: size unless !slice.first || slice.first == collection.first
86
+ links.next after: build_cursor(context: context, instance: slice.last), first: size unless !slice.last || slice.last == collection.last
87
87
 
88
88
  slice
89
89
  end
@@ -25,14 +25,9 @@ module JSONAPIonify::Api
25
25
  def define_relationship_counter(rel_name, name, include: true)
26
26
  attribute name.to_sym, types.Integer, "The number of #{rel_name}.", write: false do |_, instance, context|
27
27
  rel = context.resource.class.relationship(rel_name)
28
- blank_fields = context.fields.map { |k, _| [k, {}] }.to_h
29
28
  rel_context = rel.new(
30
29
  request: context.request,
31
- context_overrides: {
32
- owner: instance,
33
- fields: blank_fields,
34
- params: {}
35
- }
30
+ context_overrides: { owner: instance, fields: {}, params: {} }
36
31
  ).exec { |c| c }
37
32
  count = rel_context.collection.uniq.count
38
33
  case count
@@ -23,20 +23,7 @@ module JSONAPIonify::Api
23
23
  end
24
24
 
25
25
  context(:sort_params, readonly: true, persisted: true) do |context|
26
- sort_fields_from_sort_string(context.params['sort']).tap do |fields|
27
- should_error = false
28
- fields.each do |field|
29
- unless self.class.field_valid?(field.name) || field.name == id_attribute
30
- should_error = true
31
- type = self.class.type
32
- error :sort_parameter_invalid do
33
- detail "resource `#{type}` does not have field: #{field.name}"
34
- end
35
- next
36
- end
37
- end
38
- halt if should_error
39
- end
26
+ sort_fields_from_sort_string(context.params['sort'])
40
27
  end
41
28
 
42
29
  define_sorting_strategy('Object') do |collection|
@@ -91,6 +91,8 @@ module JSONAPIonify::Structure
91
91
 
92
92
  def to_json(**opts)
93
93
  Oj.dump(as_json **opts)
94
+ rescue NoMemoryError
95
+ binding.pry
94
96
  end
95
97
 
96
98
  def signature
@@ -1,3 +1,3 @@
1
1
  module JSONAPIonify
2
- VERSION = "0.10.2"
2
+ VERSION = "0.11.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsonapionify
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.2
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Waldrip
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-05-27 00:00:00.000000000 Z
11
+ date: 2016-06-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -206,6 +206,20 @@ dependencies:
206
206
  - - ! '>='
207
207
  - !ruby/object:Gem::Version
208
208
  version: '0'
209
+ - !ruby/object:Gem::Dependency
210
+ name: pry-byebug
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ! '>='
214
+ - !ruby/object:Gem::Version
215
+ version: '0'
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - ! '>='
221
+ - !ruby/object:Gem::Version
222
+ version: '0'
209
223
  - !ruby/object:Gem::Dependency
210
224
  name: rocco
211
225
  requirement: !ruby/object:Gem::Requirement
@@ -527,6 +541,15 @@ files:
527
541
  - lib/jsonapionify/api/relationship/one.rb
528
542
  - lib/jsonapionify/api/resource.rb
529
543
  - lib/jsonapionify/api/resource/builders.rb
544
+ - lib/jsonapionify/api/resource/builders/attributes_builder.rb
545
+ - lib/jsonapionify/api/resource/builders/base_builder.rb
546
+ - lib/jsonapionify/api/resource/builders/cursor_builder.rb
547
+ - lib/jsonapionify/api/resource/builders/fields_builder.rb
548
+ - lib/jsonapionify/api/resource/builders/identity_helper.rb
549
+ - lib/jsonapionify/api/resource/builders/relationship_builder.rb
550
+ - lib/jsonapionify/api/resource/builders/relationships_builder.rb
551
+ - lib/jsonapionify/api/resource/builders/resource_builder.rb
552
+ - lib/jsonapionify/api/resource/builders/resource_identifer_builder.rb
530
553
  - lib/jsonapionify/api/resource/caching.rb
531
554
  - lib/jsonapionify/api/resource/caller.rb
532
555
  - lib/jsonapionify/api/resource/class_methods.rb