jsonapi_compliable 0.6.13 → 0.7.2

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