jsonapionify 0.10.2 → 0.11.0

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