jsonapi_compliable 0.6.13 → 0.7.2

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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/docs/Jsonapi/ResourceGenerator.html +325 -0
  4. data/docs/Jsonapi.html +115 -0
  5. data/docs/JsonapiCompliable/Adapters/Abstract.html +1 -1
  6. data/docs/JsonapiCompliable/Adapters/ActiveRecord.html +59 -53
  7. data/docs/JsonapiCompliable/Adapters/ActiveRecordSideloading.html +6 -6
  8. data/docs/JsonapiCompliable/Adapters/Null.html +1 -1
  9. data/docs/JsonapiCompliable/Adapters.html +1 -1
  10. data/docs/JsonapiCompliable/Base.html +113 -117
  11. data/docs/JsonapiCompliable/Deserializer.html +87 -22
  12. data/docs/JsonapiCompliable/Errors/BadFilter.html +1 -1
  13. data/docs/JsonapiCompliable/Errors/StatNotFound.html +1 -1
  14. data/docs/JsonapiCompliable/Errors/UnsupportedPageSize.html +1 -1
  15. data/docs/JsonapiCompliable/Errors/ValidationError.html +1 -1
  16. data/docs/JsonapiCompliable/Errors.html +1 -1
  17. data/docs/JsonapiCompliable/Extensions/BooleanAttribute/ClassMethods.html +1 -1
  18. data/docs/JsonapiCompliable/Extensions/BooleanAttribute.html +1 -1
  19. data/docs/JsonapiCompliable/Extensions/ExtraAttribute/ClassMethods.html +1 -1
  20. data/docs/JsonapiCompliable/Extensions/ExtraAttribute.html +1 -1
  21. data/docs/JsonapiCompliable/Extensions.html +1 -1
  22. data/docs/JsonapiCompliable/Query.html +19 -17
  23. data/docs/JsonapiCompliable/Rails.html +1 -1
  24. data/docs/JsonapiCompliable/Resource.html +311 -220
  25. data/docs/JsonapiCompliable/Scope.html +1 -1
  26. data/docs/JsonapiCompliable/Scoping/Base.html +1 -1
  27. data/docs/JsonapiCompliable/Scoping/DefaultFilter.html +1 -1
  28. data/docs/JsonapiCompliable/Scoping/ExtraFields.html +1 -1
  29. data/docs/JsonapiCompliable/Scoping/Filter.html +1 -1
  30. data/docs/JsonapiCompliable/Scoping/Filterable.html +2 -2
  31. data/docs/JsonapiCompliable/Scoping/Paginate.html +1 -1
  32. data/docs/JsonapiCompliable/Scoping/Sort.html +1 -1
  33. data/docs/JsonapiCompliable/Scoping.html +1 -1
  34. data/docs/JsonapiCompliable/SerializableTempId.html +1 -1
  35. data/docs/JsonapiCompliable/Sideload.html +229 -78
  36. data/docs/JsonapiCompliable/Stats/DSL.html +1 -1
  37. data/docs/JsonapiCompliable/Stats/Payload.html +1 -1
  38. data/docs/JsonapiCompliable/Stats.html +1 -1
  39. data/docs/JsonapiCompliable/Util/FieldParams.html +1 -1
  40. data/docs/JsonapiCompliable/Util/Hash.html +1 -1
  41. data/docs/JsonapiCompliable/Util/IncludeParams.html +1 -1
  42. data/docs/JsonapiCompliable/Util/Persistence.html +1 -1
  43. data/docs/JsonapiCompliable/Util/RelationshipPayload.html +1 -1
  44. data/docs/JsonapiCompliable/Util/RenderOptions.html +1 -1
  45. data/docs/JsonapiCompliable/Util/ValidationResponse.html +1 -1
  46. data/docs/JsonapiCompliable/Util.html +1 -1
  47. data/docs/JsonapiCompliable.html +210 -3
  48. data/docs/_index.html +13 -1
  49. data/docs/class_list.html +1 -1
  50. data/docs/file.README.html +4 -2
  51. data/docs/index.html +4 -2
  52. data/docs/method_list.html +307 -243
  53. data/docs/top-level-namespace.html +2 -2
  54. data/lib/generators/jsonapi/resource_generator.rb +139 -58
  55. data/lib/generators/jsonapi/templates/application_resource.rb.erb +11 -2
  56. data/lib/generators/jsonapi/templates/controller.rb.erb +34 -0
  57. data/lib/generators/jsonapi/templates/destroy_request_spec.rb.erb +1 -1
  58. data/lib/generators/jsonapi/templates/payload.rb.erb +31 -0
  59. data/lib/generators/jsonapi/templates/resource.rb.erb +47 -0
  60. data/lib/generators/jsonapi/templates/serializer.rb.erb +17 -0
  61. data/lib/jsonapi_compliable/adapters/active_record_sideloading.rb +5 -5
  62. data/lib/jsonapi_compliable/resource.rb +11 -5
  63. data/lib/jsonapi_compliable/sideload.rb +40 -11
  64. data/lib/jsonapi_compliable/util/persistence.rb +9 -2
  65. data/lib/jsonapi_compliable/util/relationship_payload.rb +6 -0
  66. data/lib/jsonapi_compliable/util/validation_response.rb +2 -1
  67. data/lib/jsonapi_compliable/version.rb +1 -1
  68. metadata +4 -2
@@ -82,7 +82,7 @@
82
82
  <p class="children">
83
83
 
84
84
 
85
- <strong class="modules">Modules:</strong> <span class='object_link'><a href="JsonapiCompliable.html" title="JsonapiCompliable (module)">JsonapiCompliable</a></span>
85
+ <strong class="modules">Modules:</strong> <span class='object_link'><a href="Jsonapi.html" title="Jsonapi (module)">Jsonapi</a></span>, <span class='object_link'><a href="JsonapiCompliable.html" title="JsonapiCompliable (module)">JsonapiCompliable</a></span>
86
86
 
87
87
 
88
88
 
@@ -100,7 +100,7 @@
100
100
  </div>
101
101
 
102
102
  <div id="footer">
103
- Generated on Fri May 5 15:53:21 2017 by
103
+ Generated on Wed Jun 7 10:15:09 2017 by
104
104
  <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
105
105
  0.9.9 (ruby-2.3.0).
106
106
  </div>
@@ -2,85 +2,166 @@ module Jsonapi
2
2
  class ResourceGenerator < ::Rails::Generators::NamedBase
3
3
  source_root File.expand_path('../templates', __FILE__)
4
4
 
5
- class_option :'no-controller', type: :boolean, default: false
6
- class_option :'no-serializer', type: :boolean, default: false
7
- class_option :'no-payload', type: :boolean, default: false
8
- class_option :'no-strong-resources', type: :boolean, default: false
9
- class_option :'no-test', type: :boolean, default: false
10
-
11
- desc "This generator creates a resource file at app/resources"
5
+ class_option :'omit-comments',
6
+ type: :boolean,
7
+ default: false,
8
+ aliases: ['--omit-comments', '-c'],
9
+ desc: 'Generate without documentation comments'
10
+ class_option :'omit-controller',
11
+ type: :boolean,
12
+ default: false,
13
+ aliases: ['--omit-controller'],
14
+ desc: 'Generate without controller'
15
+ class_option :'omit-serializer',
16
+ type: :boolean,
17
+ default: false,
18
+ aliases: ['--omit-serializer', '-s'],
19
+ desc: 'Generate without serializer'
20
+ class_option :'omit-payload',
21
+ type: :boolean,
22
+ default: false,
23
+ aliases: ['--omit-payload', '-p'],
24
+ desc: 'Generate without spec payload'
25
+ class_option :'omit-strong-resource',
26
+ type: :boolean,
27
+ default: false,
28
+ aliases: ['--omit-strong-resource', '-r'],
29
+ desc: 'Generate without strong resource'
30
+ class_option :'omit-route',
31
+ type: :boolean,
32
+ default: false,
33
+ aliases: ['--omit-route'],
34
+ desc: 'Generate without specs'
35
+ class_option :'omit-tests',
36
+ type: :boolean,
37
+ default: false,
38
+ aliases: ['--omit-tests', '-t'],
39
+ desc: 'Generate without specs'
40
+
41
+ desc "This generator creates a resource file at app/resources, as well as corresponding controller/specs/route/etc"
12
42
  def copy_resource_file
13
- unless @options['no-controller']
14
- to = File.join('app/controllers', class_path, "#{file_name.pluralize}_controller.rb")
15
- template('controller.rb.erb', to)
43
+ unless model_klass
44
+ raise "You must define a #{class_name} model before generating the corresponding resource."
16
45
  end
17
46
 
18
- unless @options['no-serializer']
19
- to = File.join('app/serializers', class_path, "serializable_#{file_name}.rb")
20
- template('serializer.rb.erb', to)
21
- end
47
+ generate_controller unless omit_controller?
48
+ generate_serializer unless omit_serializer?
49
+ generate_application_resource unless application_resource_defined?
50
+ generate_spec_payload unless omit_spec_payload?
51
+ generate_strong_resource unless omit_strong_resource?
52
+ generate_route unless omit_route?
53
+ generate_tests unless omit_tests?
54
+ generate_resource
55
+ end
22
56
 
23
- unless 'ApplicationResource'.safe_constantize
24
- to = File.join('app/resources', class_path, "application_resource.rb")
25
- template('application_resource.rb.erb', to)
26
- end
57
+ private
27
58
 
28
- unless @options['no-payload']
29
- to = File.join('spec/payloads', class_path, "#{file_name}.rb")
30
- template('payload.rb.erb', to)
31
- end
59
+ def omit_comments?
60
+ @options['omit-comments']
61
+ end
62
+
63
+ def generate_controller
64
+ to = File.join('app/controllers', class_path, "#{file_name.pluralize}_controller.rb")
65
+ template('controller.rb.erb', to)
66
+ end
32
67
 
33
- unless @options['no-strong-resources']
34
- inject_into_file 'config/initializers/strong_resources.rb', after: "StrongResources.configure do\n" do <<-STR
68
+ def omit_controller?
69
+ @options['omit-controller']
70
+ end
71
+
72
+ def generate_serializer
73
+ to = File.join('app/serializers', class_path, "serializable_#{file_name}.rb")
74
+ template('serializer.rb.erb', to)
75
+ end
76
+
77
+ def omit_serializer?
78
+ @options['omit-serializer']
79
+ end
80
+
81
+ def generate_application_resource
82
+ to = File.join('app/resources', class_path, "application_resource.rb")
83
+ template('application_resource.rb.erb', to)
84
+ end
85
+
86
+ def application_resource_defined?
87
+ 'ApplicationResource'.safe_constantize.present?
88
+ end
89
+
90
+ def generate_spec_payload
91
+ to = File.join('spec/payloads', class_path, "#{file_name}.rb")
92
+ template('payload.rb.erb', to)
93
+ end
94
+
95
+ def omit_spec_payload?
96
+ @options['no-payload']
97
+ end
98
+
99
+ def generate_strong_resource
100
+ code = <<-STR
35
101
  strong_resource :#{file_name} do
36
102
  # Your attributes go here, e.g.
37
103
  # attribute :name, :string
38
104
  end
39
105
 
40
- STR
41
- end
106
+ STR
107
+ inject_into_file 'config/initializers/strong_resources.rb', after: "StrongResources.configure do\n" do
108
+ code
42
109
  end
110
+ end
111
+
112
+ def omit_strong_resource?
113
+ @options['no-strong-resources']
114
+ end
43
115
 
44
- unless @options['no-route']
45
- inject_into_file 'config/routes.rb', after: "scope '/api' do\n scope '/v1' do\n" do <<-STR
116
+ def generate_route
117
+ code = <<-STR
46
118
  resources :#{type}
47
- STR
48
- end
119
+ STR
120
+ inject_into_file 'config/routes.rb', after: "scope path: '/api' do\n scope path: '/v1' do\n" do
121
+ code
49
122
  end
123
+ end
50
124
 
51
- unless @options['no-test']
52
- to = File.join "spec/api/v1/#{file_name.pluralize}",
53
- class_path,
54
- "index_spec.rb"
55
- template('index_request_spec.rb.erb', to)
56
-
57
- to = File.join "spec/api/v1/#{file_name.pluralize}",
58
- class_path,
59
- "show_spec.rb"
60
- template('show_request_spec.rb.erb', to)
61
-
62
- to = File.join "spec/api/v1/#{file_name.pluralize}",
63
- class_path,
64
- "create_spec.rb"
65
- template('create_request_spec.rb.erb', to)
66
-
67
- to = File.join "spec/api/v1/#{file_name.pluralize}",
68
- class_path,
69
- "update_spec.rb"
70
- template('update_request_spec.rb.erb', to)
71
-
72
- to = File.join "spec/api/v1/#{file_name.pluralize}",
73
- class_path,
74
- "destroy_spec.rb"
75
- template('destroy_request_spec.rb.erb', to)
76
- end
125
+ def omit_route?
126
+ @options['no-route']
127
+ end
128
+
129
+ def generate_tests
130
+ to = File.join "spec/api/v1/#{file_name.pluralize}",
131
+ class_path,
132
+ "index_spec.rb"
133
+ template('index_request_spec.rb.erb', to)
134
+
135
+ to = File.join "spec/api/v1/#{file_name.pluralize}",
136
+ class_path,
137
+ "show_spec.rb"
138
+ template('show_request_spec.rb.erb', to)
139
+
140
+ to = File.join "spec/api/v1/#{file_name.pluralize}",
141
+ class_path,
142
+ "create_spec.rb"
143
+ template('create_request_spec.rb.erb', to)
144
+
145
+ to = File.join "spec/api/v1/#{file_name.pluralize}",
146
+ class_path,
147
+ "update_spec.rb"
148
+ template('update_request_spec.rb.erb', to)
149
+
150
+ to = File.join "spec/api/v1/#{file_name.pluralize}",
151
+ class_path,
152
+ "destroy_spec.rb"
153
+ template('destroy_request_spec.rb.erb', to)
154
+ end
77
155
 
156
+ def omit_tests?
157
+ @options['no-test']
158
+ end
159
+
160
+ def generate_resource
78
161
  to = File.join('app/resources', class_path, "#{file_name}_resource.rb")
79
162
  template('resource.rb.erb', to)
80
163
  end
81
164
 
82
- private
83
-
84
165
  def model_klass
85
166
  class_name.safe_constantize
86
167
  end
@@ -1,5 +1,14 @@
1
- require 'jsonapi_compliable/adapters/active_record'
2
-
1
+ <%- unless omit_comments? -%>
2
+ # ApplicationResource is similar to ApplicationRecord - a base class that
3
+ # holds configuration/methods for subclasses.
4
+ # All Resources should inherit from ApplicationResource.
5
+ # Resource documentation: https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Resource.html
6
+ <%- end -%>
3
7
  class ApplicationResource < JsonapiCompliable::Resource
8
+ <%- unless omit_comments? -%>
9
+ # Use the ActiveRecord Adapter for all subclasses.
10
+ # Subclasses can still override this default.
11
+ # More on adapters: https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Adapters/Abstract.html
12
+ <%- end -%>
4
13
  use_adapter JsonapiCompliable::Adapters::ActiveRecord
5
14
  end
@@ -1,21 +1,46 @@
1
1
  <% module_namespacing do -%>
2
2
  class <%= model_klass.name.pluralize %>Controller < ApplicationController
3
+ <%- unless omit_comments? -%>
4
+ # Mark this as a JSONAPI controller, associating with the given resource
5
+ <%- end -%>
3
6
  jsonapi resource: <%= model_klass %>Resource
4
7
 
8
+ <%- unless omit_comments? -%>
9
+ # Reference a strong resource payload defined in
10
+ # config/initializers/strong_resources.rb
11
+ <%- end -%>
5
12
  strong_resource :<%= file_name %>
6
13
 
14
+ <%- unless omit_comments? -%>
15
+ # Run strong parameter validation for these actions.
16
+ # Invalid keys will be dropped.
17
+ # Invalid value types will log or raise based on the configuration
18
+ # ActionController::Parameters.action_on_invalid_parameters
19
+ <%- end -%>
7
20
  before_action :apply_strong_params, only: [:create, :update]
8
21
 
22
+ <%- unless omit_comments? -%>
23
+ # Start with a base scope and pass to render_jsonapi
24
+ <%- end -%>
9
25
  def index
10
26
  <%= file_name.pluralize %> = <%= model_klass %>.all
11
27
  render_jsonapi(<%= file_name.pluralize %>)
12
28
  end
13
29
 
30
+ <%- unless omit_comments? -%>
31
+ # Call jsonapi_scope directly here so we can get behavior like
32
+ # sparse fieldsets and statistics.
33
+ <%- end -%>
14
34
  def show
15
35
  scope = jsonapi_scope(<%= model_klass %>.where(id: params[:id]))
16
36
  render_jsonapi(scope.resolve.first, scope: false)
17
37
  end
18
38
 
39
+ <%- unless omit_comments? -%>
40
+ # jsonapi_create will use the configured Resource (and adapter) to persist.
41
+ # This will handle nested relationships as well.
42
+ # On validation errors, render correct error JSON.
43
+ <%- end -%>
19
44
  def create
20
45
  <%= file_name %>, success = jsonapi_create.to_a
21
46
 
@@ -26,6 +51,11 @@ class <%= model_klass.name.pluralize %>Controller < ApplicationController
26
51
  end
27
52
  end
28
53
 
54
+ <%- unless omit_comments? -%>
55
+ # jsonapi_update will use the configured Resource (and adapter) to persist.
56
+ # This will handle nested relationships as well.
57
+ # On validation errors, render correct error JSON.
58
+ <%- end -%>
29
59
  def update
30
60
  <%= file_name %>, success = jsonapi_update.to_a
31
61
 
@@ -36,6 +66,10 @@ class <%= model_klass.name.pluralize %>Controller < ApplicationController
36
66
  end
37
67
  end
38
68
 
69
+ <%- unless omit_comments? -%>
70
+ # No need for any special logic here as no_content is jsonapi_compliant.
71
+ # Customize this if you have a more complex use case.
72
+ <%- end -%>
39
73
  def destroy
40
74
  <%= file_name %> = <%= model_klass %>.find(params[:id])
41
75
  <%= file_name %>.destroy
@@ -2,7 +2,7 @@ require 'rails_helper'
2
2
 
3
3
  RSpec.describe "<%= type %>#destroy", type: :request do
4
4
  context 'basic destroy' do
5
- let!(:<%= file_name %>) { create(:<%= file_name %>) }
5
+ let!(:<%= file_name %>) { FactoryGirl.create(:<%= file_name %>) }
6
6
 
7
7
  it 'updates the resource' do
8
8
  expect {
@@ -1,2 +1,33 @@
1
+ <%- unless omit_comments? -%>
2
+ # Register a payload to validate against.
3
+ # Add expected attributes within this block, e.g.:
4
+ #
5
+ # key(:name)
6
+ #
7
+ # Optionally validate the type as well:
8
+ #
9
+ # key(:name, String)
10
+ #
11
+ # This will:
12
+ #
13
+ # * Compare record.name == json['name']
14
+ # * Ensure no extra keys are in the json payload
15
+ # * Ensure no values are nil (unless allow_nil: true is passed)
16
+ # * Ensures json['name'] is a string
17
+ #
18
+ # If you have custom serialization logic and want to compare against
19
+ # something other than "record.name", pass a block:
20
+ #
21
+ # key(:name) { |record| record.name.upcase }
22
+ #
23
+ # Or, if this is a one-off for a particular spec, do that customization at
24
+ # runtime:
25
+ #
26
+ # assert_payload(:person, person_record, json_item) do
27
+ # key(:name) { 'Homer Simpson' }
28
+ # end
29
+ #
30
+ # For more information, see https://jsonapi-suite.github.io/jsonapi_spec_helpers/
31
+ <%- end -%>
1
32
  JsonapiSpecHelpers::Payload.register(:<%= file_name %>) do
2
33
  end
@@ -1,6 +1,53 @@
1
+ <%- unless omit_comments? -%>
2
+ # Define how to query and persist a given model.
3
+ # Further Resource documentation: https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Resource.html
4
+ <%- end -%>
1
5
  <% module_namespacing do -%>
2
6
  class <%= class_name %>Resource < ApplicationResource
7
+ <%- unless omit_comments? -%>
8
+ # Used for associating this resource with a given input.
9
+ # This should match the 'type' in the corresponding serializer.
10
+ <%- end -%>
3
11
  type :<%= type %>
12
+ <%- unless omit_comments? -%>
13
+ # Associate to a Model object so we know how to persist.
14
+ <%- end -%>
4
15
  model <%= model_klass %>
16
+ <%- unless omit_comments? -%>
17
+ # Customize your resource here. Some common examples:
18
+ #
19
+ # === Allow ?filter[name] query parameter ===
20
+ # allow_filter :name
21
+ #
22
+ # === Enable total count, when requested ===
23
+ # allow_stat total: [:count]
24
+ #
25
+ # === Allow sideloading/sideposting of relationships ===
26
+ # belongs_to :foo,
27
+ # foreign_key: :foo_id,
28
+ # resource: FooResource,
29
+ # scope: -> { Foo.all }
30
+ #
31
+ # === Custom sorting logic ===
32
+ # sort do |scope, att, dir|
33
+ # ... code ...
34
+ # end
35
+ #
36
+ # === Change default sort ===
37
+ # default_sort([{ title: :asc }])
38
+ #
39
+ # === Custom pagination logic ===
40
+ # paginate do |scope, current_page, per_page|
41
+ # ... code ...
42
+ # end
43
+ #
44
+ # === Change default page size ===
45
+ # default_page_size(10)
46
+ #
47
+ # === Change how we resolve the scope ===
48
+ # def resolve(scope)
49
+ # ... code ...
50
+ # end
51
+ <%- end -%>
5
52
  end
6
53
  <% end -%>
@@ -1,5 +1,22 @@
1
+ <%- unless omit_comments? -%>
2
+ # Serializers define the rendered JSON for a model instance.
3
+ # We use jsonapi-rb, which is similar to active_model_serializers.
4
+ <%- end -%>
1
5
  <% module_namespacing do -%>
2
6
  class Serializable<%= class_name %> < JSONAPI::Serializable::Resource
3
7
  type :<%= type %>
8
+
9
+ <%- unless omit_comments? -%>
10
+ # Add attributes here to ensure they get rendered, .e.g.
11
+ #
12
+ # attribute :name
13
+ #
14
+ # To customize, pass a block and reference the underlying @object
15
+ # being serialized:
16
+ #
17
+ # attribute :name do
18
+ # @object.name.upcase
19
+ # end
20
+ <%- end -%>
4
21
  end
5
22
  <% end -%>
@@ -47,7 +47,7 @@ module JsonapiCompliable
47
47
  def has_one(association_name, scope: nil, resource:, foreign_key:, primary_key: :id, &blk)
48
48
  _scope = scope
49
49
 
50
- allow_sideload association_name, resource: resource do
50
+ allow_sideload association_name, type: :has_one, foreign_key: foreign_key, primary_key: primary_key, resource: resource do
51
51
  scope do |parents|
52
52
  parent_ids = parents.map { |p| p.send(primary_key) }
53
53
  _scope.call.where(foreign_key => parent_ids.uniq.compact)
@@ -71,7 +71,7 @@ module JsonapiCompliable
71
71
  fk = foreign_key.values.first
72
72
  _scope = scope
73
73
 
74
- allow_sideload association_name, resource: resource do
74
+ allow_sideload association_name, type: :habtm, foreign_key: foreign_key, primary_key: primary_key, resource: resource do
75
75
  scope do |parents|
76
76
  parent_ids = parents.map { |p| p.send(primary_key) }
77
77
  parent_ids.uniq!
@@ -94,14 +94,14 @@ module JsonapiCompliable
94
94
  end
95
95
 
96
96
  def polymorphic_belongs_to(association_name, group_by:, groups:, &blk)
97
- allow_sideload association_name, polymorphic: true do
98
- group_by(&group_by)
97
+ allow_sideload association_name, type: :polymorphic_belongs_to, polymorphic: true do
98
+ group_by(group_by)
99
99
 
100
100
  groups.each_pair do |type, config|
101
101
  primary_key = config[:primary_key] || :id
102
102
  foreign_key = config[:foreign_key]
103
103
 
104
- allow_sideload type, resource: config[:resource] do
104
+ allow_sideload type, parent: self, primary_key: primary_key, foreign_key: foreign_key, type: :belongs_to, resource: config[:resource] do
105
105
  scope do |parents|
106
106
  parent_ids = parents.map { |p| p.send(foreign_key) }
107
107
  parent_ids.compact!
@@ -87,8 +87,6 @@ module JsonapiCompliable
87
87
  # scope: -> { Comment.all },
88
88
  # resource: CommentResource,
89
89
  # foreign_key: :post_id
90
- #
91
- # @attr_reader [Hash] context A hash of +object+ and +namespace+ - Example object is a Rails controller, example namespace would be +:index+ or +:show+
92
90
  class Resource
93
91
  extend Forwardable
94
92
  attr_reader :context
@@ -451,16 +449,24 @@ module JsonapiCompliable
451
449
  end
452
450
  end
453
451
 
454
- # The current context set by +#with_context+ in the form of
452
+ # The current context **object** set by +#with_context+. If you are
453
+ # using Rails, this is a controller instance.
455
454
  #
456
- # { object: context_obj, namespace: :index }
455
+ # This method is equivalent to +JsonapiCompliable.context[:object]+
457
456
  #
458
457
  # @see #with_context
459
- # @return [Hash] the context hash
458
+ # @return the context object
460
459
  def context
461
460
  JsonapiCompliable.context[:object]
462
461
  end
463
462
 
463
+ # The current context **namespace** set by +#with_context+. If you
464
+ # are using Rails, this is the controller method name (e.g. +:index+)
465
+ #
466
+ # This method is equivalent to +JsonapiCompliable.context[:namespace]+
467
+ #
468
+ # @see #with_context
469
+ # @return [Symbol] the context namespace
464
470
  def context_namespace
465
471
  JsonapiCompliable.context[:namespace]
466
472
  end
@@ -6,7 +6,7 @@ module JsonapiCompliable
6
6
  # @attr_reader [Hash] sideloads The associated sibling sideloads
7
7
  # @attr_reader [Proc] scope_proc The configured 'scope' block
8
8
  # @attr_reader [Proc] assign_proc The configured 'assign' block
9
- # @attr_reader [Proc] grouper The configured 'group_by' proc
9
+ # @attr_reader [Symbol] grouping_field The configured 'group_by' symbol
10
10
  # @attr_reader [Symbol] foreign_key The attribute used to match objects - need not be a true database foreign key.
11
11
  # @attr_reader [Symbol] primary_key The attribute used to match objects - need not be a true database primary key.
12
12
  # @attr_reader [Symbol] type One of :has_many, :belongs_to, etc
@@ -15,10 +15,11 @@ module JsonapiCompliable
15
15
  :resource_class,
16
16
  :polymorphic,
17
17
  :polymorphic_groups,
18
+ :parent,
18
19
  :sideloads,
19
20
  :scope_proc,
20
21
  :assign_proc,
21
- :grouper,
22
+ :grouping_field,
22
23
  :foreign_key,
23
24
  :primary_key,
24
25
  :type
@@ -28,12 +29,13 @@ module JsonapiCompliable
28
29
  # An anonymous Resource will be assigned when none provided.
29
30
  #
30
31
  # @see Adapters::Abstract#sideloading_module
31
- def initialize(name, type: nil, resource: nil, polymorphic: false, primary_key: :id, foreign_key: nil)
32
+ def initialize(name, type: nil, resource: nil, polymorphic: false, primary_key: :id, foreign_key: nil, parent: nil)
32
33
  @name = name
33
34
  @resource_class = (resource || Class.new(Resource))
34
35
  @sideloads = {}
35
36
  @polymorphic = !!polymorphic
36
37
  @polymorphic_groups = {} if polymorphic?
38
+ @parent = parent
37
39
  @primary_key = primary_key
38
40
  @foreign_key = foreign_key
39
41
  @type = type
@@ -55,7 +57,7 @@ module JsonapiCompliable
55
57
  # +Business+ or +Government+:
56
58
  #
57
59
  # allow_sideload :organization, :polymorphic: true do
58
- # group_by { |record| record.organization_type }
60
+ # group_by :organization_type
59
61
  #
60
62
  # allow_sideload 'Business', resource: BusinessResource do
61
63
  # # ... code ...
@@ -70,7 +72,7 @@ module JsonapiCompliable
70
72
  # with ActiveRecord:
71
73
  #
72
74
  # polymorphic_belongs_to :organization,
73
- # group_by: ->(office) { office.organization_type },
75
+ # group_by: :organization_type,
74
76
  # groups: {
75
77
  # 'Business' => {
76
78
  # scope: -> { Business.all },
@@ -181,21 +183,25 @@ module JsonapiCompliable
181
183
  # @see #name
182
184
  # @see #type
183
185
  def associate(parent, child)
184
- resource_class.config[:adapter].associate(parent, child, name, type)
186
+ association_name = @parent ? @parent.name : name
187
+ resource_class.config[:adapter].associate parent,
188
+ child,
189
+ association_name,
190
+ type
185
191
  end
186
192
 
187
- # Define a proc that groups the parent records. For instance, with
193
+ # Define an attribute that groups the parent records. For instance, with
188
194
  # an ActiveRecord polymorphic belongs_to there will be a +parent_id+
189
195
  # and +parent_type+. We would want to group on +parent_type+:
190
196
  #
191
197
  # allow_sideload :organization, polymorphic: true do
192
198
  # # group parent_type, parent here is 'organization'
193
- # group_by ->(office) { office.organization_type }
199
+ # group_by :organization_type
194
200
  # end
195
201
  #
196
202
  # @see #polymorphic?
197
- def group_by(&grouper)
198
- @grouper = grouper
203
+ def group_by(grouping_field)
204
+ @grouping_field = grouping_field
199
205
  end
200
206
 
201
207
  # Resolve the sideload.
@@ -323,6 +329,13 @@ module JsonapiCompliable
323
329
  result
324
330
  end
325
331
 
332
+ # @api private
333
+ def polymorphic_child_for_type(type)
334
+ polymorphic_groups.values.find do |v|
335
+ v.resource_class.config[:type] == type.to_sym
336
+ end
337
+ end
338
+
326
339
  private
327
340
 
328
341
  def nested_sideload_hash(sideload, processed)
@@ -333,8 +346,24 @@ module JsonapiCompliable
333
346
  end
334
347
  end
335
348
 
349
+ def polymorphic_grouper(grouping_field)
350
+ lambda do |record|
351
+ if record.is_a?(Hash)
352
+ if record.keys[0].is_a?(Symbol)
353
+ record[grouping_field]
354
+ else
355
+ record[grouping_field.to_s]
356
+ end
357
+ else
358
+ record.send(grouping_field)
359
+ end
360
+ end
361
+ end
362
+
336
363
  def resolve_polymorphic(parents, query)
337
- parents.group_by(&@grouper).each_pair do |group_type, group_members|
364
+ grouper = polymorphic_grouper(@grouping_field)
365
+
366
+ parents.group_by(&grouper).each_pair do |group_type, group_members|
338
367
  sideload_for_group = @polymorphic_groups[group_type]
339
368
  if sideload_for_group
340
369
  sideload_for_group.resolve(group_members, query, name)