jpie 2.0.2 → 2.1.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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/nasa-power-of-ten-ruby/SKILL.md +71 -0
  3. data/.claude/skills/root-cause-analysis/SKILL.md +62 -0
  4. data/.claude/skills/skills-and-subagents/SKILL.md +54 -0
  5. data/.cursor/agents/bias-reviewer.md +58 -0
  6. data/.cursor/agents/lint-format.md +58 -0
  7. data/.cursor/agents/nasa-power-of-ten-reviewer.md +38 -0
  8. data/.cursor/agents/systematic-debugging.md +52 -0
  9. data/Gemfile.lock +1 -1
  10. data/README.md +114 -0
  11. data/lib/json_api/controllers/concerns/controller_helpers/error_rendering.rb +18 -0
  12. data/lib/json_api/controllers/concerns/resource_actions/crud_helpers.rb +17 -0
  13. data/lib/json_api/controllers/concerns/resource_actions/resource_loading.rb +3 -2
  14. data/lib/json_api/controllers/concerns/resource_actions/serialization.rb +24 -0
  15. data/lib/json_api/controllers/concerns/resource_actions/sideposting.rb +97 -0
  16. data/lib/json_api/controllers/concerns/resource_actions/sideposting_primary_first.rb +63 -0
  17. data/lib/json_api/controllers/concerns/resource_actions.rb +18 -4
  18. data/lib/json_api/railtie.rb +2 -0
  19. data/lib/json_api/resources/resource.rb +4 -0
  20. data/lib/json_api/resources/resource_loader.rb +10 -21
  21. data/lib/json_api/routing.rb +2 -2
  22. data/lib/json_api/serialization/concerns/deserialization_helpers.rb +11 -2
  23. data/lib/json_api/serialization/concerns/relationship_processing.rb +1 -1
  24. data/lib/json_api/serialization/concerns/relationships_deserialization.rb +12 -0
  25. data/lib/json_api/sideposting/lid_resolver.rb +72 -0
  26. data/lib/json_api/sideposting/order.rb +11 -0
  27. data/lib/json_api/sideposting/processor.rb +118 -0
  28. data/lib/json_api/support/relationship_helpers.rb +4 -0
  29. data/lib/json_api/support/resource_identifier.rb +4 -0
  30. data/lib/json_api/version.rb +1 -1
  31. data/lib/json_api.rb +6 -1
  32. metadata +13 -1
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json_api/sideposting/order"
4
+ require_relative "sideposting_primary_first"
5
+
6
+ module JSONAPI
7
+ module ResourceActions
8
+ module Sideposting
9
+ extend ActiveSupport::Concern
10
+ include SidepostingPrimaryFirst
11
+
12
+ class_methods do
13
+ def sidepost_order(value)
14
+ order = value.to_sym
15
+ unless JSONAPI::Sideposting::Order::VALID_ORDERS.include?(order)
16
+ raise ArgumentError, "sidepost_order must be one of #{JSONAPI::Sideposting::Order::VALID_ORDERS.join(", ")}"
17
+ end
18
+
19
+ @sidepost_order = order
20
+ end
21
+
22
+ def sidepost_order_value
23
+ @sidepost_order || JSONAPI::Sideposting::Order::PRIMARY_FIRST
24
+ end
25
+
26
+ def included_first?
27
+ sidepost_order_value == JSONAPI::Sideposting::Order::INCLUDED_FIRST
28
+ end
29
+ end
30
+
31
+ def sidepost_request?
32
+ params[:included].present?
33
+ end
34
+
35
+ def create_with_sidepost
36
+ if self.class.included_first?
37
+ create_with_sidepost_included_first
38
+ else
39
+ create_with_sidepost_primary_first
40
+ end
41
+ end
42
+
43
+ def create_with_sidepost_included_first
44
+ ActiveRecord::Base.transaction do
45
+ processor = JSONAPI::Sideposting::Processor.new(included: params[:included], controller: self)
46
+ processor.create_all!
47
+ resolved_data = processor.lid_resolver.resolve(raw_jsonapi_data)
48
+ resource = build_resource_from_resolved_data(resolved_data)
49
+ authorize_resource_action!(resource, action: :create)
50
+ attach_active_storage_files(resource, @create_attachments, resource_class: determine_sti_resource_class)
51
+ save_created_with_sidepost(resource, processor.created_records)
52
+ end
53
+ end
54
+
55
+ def extract_lid_relationships!(data)
56
+ extracted = {}
57
+ rels = data[:relationships] || {}
58
+ rels.each do |key, val|
59
+ next unless val.is_a?(Hash)
60
+
61
+ d = val[:data] || val["data"]
62
+ next unless contains_lid?(d)
63
+
64
+ extracted[key] = val
65
+ end
66
+ extracted.each_key { |k| rels.delete(k) }
67
+ extracted
68
+ end
69
+
70
+ def contains_lid?(rel_data)
71
+ return false if rel_data.blank?
72
+
73
+ if rel_data.is_a?(Array)
74
+ rel_data.any? { |item| contains_lid?(item) }
75
+ else
76
+ (rel_data[:lid] || rel_data["lid"]).to_s.present?
77
+ end
78
+ end
79
+
80
+ def apply_resolved_relationships!(resource, resolved_relationships)
81
+ payload = { relationships: resolved_relationships }
82
+ params_hash = JSONAPI::Deserializer.new(
83
+ payload,
84
+ model_class: resource.class,
85
+ action: :update,
86
+ ).to_model_attributes
87
+ resource.update!(params_hash)
88
+ end
89
+
90
+ def build_resource_from_resolved_data(resolved_data)
91
+ sti_class = determine_sti_class
92
+ params_hash, @create_attachments = prepare_create_params_from_data(sti_class, resolved_data)
93
+ sti_class.new(params_hash)
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONAPI
4
+ module ResourceActions
5
+ module SidepostingPrimaryFirst
6
+ extend ActiveSupport::Concern
7
+
8
+ def create_with_sidepost_primary_first
9
+ ActiveRecord::Base.transaction do
10
+ data = raw_jsonapi_data.dup
11
+ lid_relationships = extract_lid_relationships!(data)
12
+ resource, lid_resolver = create_primary_and_lid_resolver(data)
13
+ processor = run_sidepost_processor(lid_resolver)
14
+ apply_lid_relationships_if_any(resource, lid_relationships, lid_resolver)
15
+ render_created_with_sidepost(resource, processor)
16
+ end
17
+ end
18
+
19
+ def create_primary_and_lid_resolver(data)
20
+ resource = build_resource_from_resolved_data(data)
21
+ authorize_resource_action!(resource, action: :create)
22
+ attach_active_storage_files(resource, @create_attachments, resource_class: determine_sti_resource_class)
23
+ raise JSONAPI::Sideposting::PrimaryValidationError, resource unless resource.save
24
+
25
+ [resource, build_lid_resolver_with_primary(data, resource)]
26
+ end
27
+
28
+ def build_lid_resolver_with_primary(data, resource)
29
+ lid_resolver = JSONAPI::Sideposting::LidResolver.new
30
+ primary_lid = (data[:lid] || data["lid"]).to_s.presence
31
+ if primary_lid
32
+ type_name = RelationshipHelpers.resource_type_name(determine_sti_resource_class)
33
+ lid_resolver.add(primary_lid, type: type_name, id: resource.id)
34
+ end
35
+ lid_resolver
36
+ end
37
+
38
+ def run_sidepost_processor(lid_resolver)
39
+ processor = JSONAPI::Sideposting::Processor.new(
40
+ included: params[:included],
41
+ controller: self,
42
+ lid_resolver: lid_resolver,
43
+ )
44
+ processor.create_all!
45
+ processor
46
+ end
47
+
48
+ def apply_lid_relationships_if_any(resource, lid_relationships, lid_resolver)
49
+ return if lid_relationships.blank?
50
+
51
+ resolved = lid_resolver.resolve(lid_relationships)
52
+ apply_resolved_relationships!(resource, resolved)
53
+ end
54
+
55
+ def render_created_with_sidepost(resource, processor)
56
+ emit_resource_event(:created, resource)
57
+ render json: serialize_resource_with_sidepost(resource, processor.created_records),
58
+ status: :created,
59
+ location: resource_url(resource)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -9,6 +9,7 @@ require_relative "resource_actions/pagination"
9
9
  require_relative "resource_actions/type_validation"
10
10
  require_relative "resource_actions/crud_helpers"
11
11
  require_relative "resource_actions/resource_loading"
12
+ require_relative "resource_actions/sideposting"
12
13
 
13
14
  module JSONAPI
14
15
  module ResourceActions
@@ -22,6 +23,7 @@ module JSONAPI
22
23
  include TypeValidation
23
24
  include CrudHelpers
24
25
  include ResourceLoading
26
+ include Sideposting
25
27
 
26
28
  included do
27
29
  before_action :load_jsonapi_resource
@@ -47,16 +49,28 @@ module JSONAPI
47
49
  end
48
50
 
49
51
  def create
50
- resource = build_resource_for_create
51
- authorize_resource_action!(resource, action: :create)
52
- attach_active_storage_files(resource, @create_attachments, resource_class: determine_sti_resource_class)
53
- save_created(resource)
52
+ perform_create
54
53
  rescue ArgumentError => e
55
54
  render_create_error(e)
55
+ rescue JSONAPI::Sideposting::ValidationError => e
56
+ render_sidepost_validation_errors(e)
57
+ rescue JSONAPI::Sideposting::PrimaryValidationError => e
58
+ render_validation_errors(e.record)
56
59
  rescue JSONAPI::Errors::ParameterNotAllowed, ActiveSupport::MessageVerifier::InvalidSignature => e
57
60
  handle_create_exception(e)
58
61
  end
59
62
 
63
+ def perform_create
64
+ if sidepost_request?
65
+ create_with_sidepost
66
+ else
67
+ resource = build_resource_for_create
68
+ authorize_resource_action!(resource, action: :create)
69
+ attach_active_storage_files(resource, @create_attachments, resource_class: determine_sti_resource_class)
70
+ save_created(resource)
71
+ end
72
+ end
73
+
60
74
  def build_resource_for_create
61
75
  sti_class = determine_sti_class
62
76
  params_hash, @create_attachments = prepare_create_params(sti_class)
@@ -132,6 +132,8 @@ module JSONAPI
132
132
  load "json_api/controllers/base_controller.rb"
133
133
  JSONAPI.send(:remove_const, :RelationshipsController)
134
134
  load "json_api/controllers/relationships_controller.rb"
135
+ JSONAPI.send(:remove_const, :ResourcesController)
136
+ load "json_api/controllers/resources_controller.rb"
135
137
  end
136
138
 
137
139
  def include_jsonapi_concerns(base_controller_class)
@@ -23,6 +23,10 @@ module JSONAPI
23
23
  super
24
24
  subclass.type_format = type_format
25
25
  end
26
+
27
+ def sidepost_create(*)
28
+ nil
29
+ end
26
30
  end
27
31
 
28
32
  def initialize(record = nil, context = {})
@@ -16,25 +16,7 @@ module JSONAPI
16
16
  end
17
17
  end
18
18
 
19
- # Thread-safe caches for resource class lookups
20
- # Using simple Hash with ||= is safe enough - worst case is duplicate computation
21
- # which is acceptable since the result is always the same Class object
22
- @find_cache = {}
23
- @model_cache = {}
24
-
25
- class << self
26
- def clear_cache!
27
- @find_cache = {}
28
- @model_cache = {}
29
- end
30
- end
31
-
32
19
  def self.find(resource_type, namespace: nil)
33
- cache_key = "#{namespace}::#{resource_type}"
34
- @find_cache[cache_key] ||= find_uncached(resource_type, namespace:)
35
- end
36
-
37
- def self.find_uncached(resource_type, namespace: nil)
38
20
  return find_namespaced(resource_type, namespace) if namespace.present?
39
21
 
40
22
  find_flat(resource_type, namespace)
@@ -66,8 +48,7 @@ module JSONAPI
66
48
  def self.find_for_model(model_class, namespace: nil)
67
49
  return ActiveStorageBlobResource if active_storage_blob?(model_class)
68
50
 
69
- cache_key = "#{namespace}::#{model_class.name}"
70
- @model_cache[cache_key] ||= find_resource_for_model(model_class, namespace)
51
+ find_resource_for_model(model_class, namespace)
71
52
  end
72
53
 
73
54
  def self.find_resource_for_model(model_class, namespace)
@@ -76,7 +57,15 @@ module JSONAPI
76
57
 
77
58
  find(resource_type, namespace: effective_namespace)
78
59
  rescue MissingResourceClass
79
- find_base_class_resource(model_class, effective_namespace)
60
+ try_flat_full_name_resource(model_class) || find_base_class_resource(model_class, effective_namespace)
61
+ end
62
+
63
+ def self.try_flat_full_name_resource(model_class)
64
+ return nil unless model_class.name.include?("::")
65
+
66
+ "#{model_class.name.gsub("::", "")}Resource".constantize
67
+ rescue NameError
68
+ nil
80
69
  end
81
70
 
82
71
  def self.find_base_class_resource(model_class, effective_namespace)
@@ -85,9 +85,9 @@ module JSONAPI
85
85
  next if sub_resource_name == resource.to_sym
86
86
 
87
87
  jsonapi_resources(sub_resource_name, defaults:)
88
+ rescue NameError, JSONAPI::ResourceLoader::MissingResourceClass
89
+ next
88
90
  end
89
- rescue NameError, JSONAPI::ResourceLoader::MissingResourceClass
90
- nil
91
91
  end
92
92
  end
93
93
  end
@@ -52,6 +52,11 @@ module JSONAPI
52
52
 
53
53
  def extract_id(resource_identifier)
54
54
  id = RelationshipHelpers.extract_id_from_identifier(resource_identifier)
55
+ if id.nil? && lid?(resource_identifier)
56
+ raise ArgumentError,
57
+ "lid references must be resolved before deserialization " \
58
+ "(include related resources in top-level included)"
59
+ end
55
60
  raise ArgumentError, "Missing id in relationship data" unless id
56
61
 
57
62
  id
@@ -75,11 +80,15 @@ module JSONAPI
75
80
  def valid_resource_identifier?(identifier)
76
81
  return false unless identifier.is_a?(Hash)
77
82
 
78
- has_id?(identifier) && has_type?(identifier)
83
+ has_type?(identifier) && (has_id?(identifier) || lid?(identifier))
79
84
  end
80
85
 
81
86
  def has_id?(identifier)
82
- identifier[:id]
87
+ identifier[:id].present?
88
+ end
89
+
90
+ def lid?(identifier)
91
+ identifier[:lid].to_s.present?
83
92
  end
84
93
 
85
94
  def has_type?(identifier)
@@ -47,7 +47,7 @@ module JSONAPI
47
47
  def validate_relationship_data_format!(data, association_name)
48
48
  return if valid_relationship_data?(data)
49
49
 
50
- raise ArgumentError, "Invalid relationship data for #{association_name}: missing type or id"
50
+ raise ArgumentError, "Invalid relationship data for #{association_name}: missing type or (id or lid)"
51
51
  end
52
52
 
53
53
  def process_relationship_data(attrs, association_name, param_name, data)
@@ -35,10 +35,22 @@ module JSONAPI
35
35
  end
36
36
  end
37
37
 
38
+ def extract_identifiers_from_data(data)
39
+ if data.is_a?(Array)
40
+ data.map { |r| r.to_h.symbolize_keys }
41
+ else
42
+ [data.to_h.symbolize_keys]
43
+ end
44
+ end
45
+
38
46
  def extract_id_from_identifier(identifier)
39
47
  RelationshipHelpers.extract_id_from_identifier(identifier)
40
48
  end
41
49
 
50
+ def extract_lid_from_identifier(identifier)
51
+ RelationshipHelpers.extract_lid_from_identifier(identifier)
52
+ end
53
+
42
54
  def relationship_id(relationship_name)
43
55
  relationship_ids(relationship_name).first
44
56
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONAPI
4
+ module Sideposting
5
+ class LidResolver
6
+ def initialize
7
+ @map = {}
8
+ end
9
+
10
+ def add(lid, type:, id:)
11
+ raise ArgumentError, "lid is required" if lid.blank?
12
+ raise ArgumentError, "type is required" if type.blank?
13
+ raise ArgumentError, "id is required" if id.blank?
14
+
15
+ @map[lid.to_s] = { type: type.to_s, id: id.to_s }
16
+ self
17
+ end
18
+
19
+ def resolve(data)
20
+ return data if data.nil?
21
+
22
+ case data
23
+ when Hash
24
+ resolve_hash(data)
25
+ when Array
26
+ data.map { |item| resolve(item) }
27
+ else
28
+ data
29
+ end
30
+ end
31
+
32
+ def [](lid)
33
+ @map[lid.to_s]
34
+ end
35
+
36
+ private
37
+
38
+ def resolve_hash(hash)
39
+ result = {}
40
+ hash.each do |key, value|
41
+ result[key] = if resource_identifier_with_lid?(value)
42
+ resolve_identifier(value)
43
+ else
44
+ resolve(value)
45
+ end
46
+ end
47
+ result
48
+ end
49
+
50
+ def resource_identifier_with_lid?(value)
51
+ return false unless value.is_a?(Hash)
52
+
53
+ value = value.symbolize_keys if value.respond_to?(:symbolize_keys)
54
+ type = value[:type] || value["type"]
55
+ lid = value[:lid] || value["lid"]
56
+ type.present? && lid.to_s.present?
57
+ end
58
+
59
+ def resolve_identifier(identifier)
60
+ identifier = identifier.symbolize_keys if identifier.respond_to?(:symbolize_keys)
61
+ lid = (identifier[:lid] || identifier["lid"]).to_s
62
+ resolved = @map[lid]
63
+ unless resolved
64
+ raise ArgumentError,
65
+ "Unknown lid '#{lid}' (ensure the resource is in the top-level included array)"
66
+ end
67
+
68
+ { type: resolved[:type], id: resolved[:id] }
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONAPI
4
+ module Sideposting
5
+ module Order
6
+ PRIMARY_FIRST = :primary_first
7
+ INCLUDED_FIRST = :included_first
8
+ VALID_ORDERS = [PRIMARY_FIRST, INCLUDED_FIRST].freeze
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json_api/sideposting/lid_resolver"
4
+
5
+ module JSONAPI
6
+ module Sideposting
7
+ class ValidationError < JSONAPI::Error
8
+ attr_reader :record, :included_index
9
+
10
+ def initialize(record:, included_index:)
11
+ @record = record
12
+ @included_index = included_index
13
+ super("Validation failed for included resource at index #{included_index}")
14
+ end
15
+ end
16
+
17
+ class PrimaryValidationError < JSONAPI::Error
18
+ attr_reader :record
19
+
20
+ def initialize(record)
21
+ @record = record
22
+ super("Validation failed for primary resource")
23
+ end
24
+ end
25
+
26
+ class Processor
27
+ def initialize(included:, controller:, lid_resolver: nil)
28
+ raise ArgumentError, "included is required" if included.blank?
29
+ raise ArgumentError, "controller is required" if controller.nil?
30
+
31
+ @included = Array(included).map { |item| symbolize_item(item) }
32
+ @controller = controller
33
+ @lid_resolver = lid_resolver || LidResolver.new
34
+ @created_records = []
35
+ end
36
+
37
+ def create_all!
38
+ @included.each_with_index do |item, index|
39
+ record = create_one(item, index)
40
+ @created_records << record
41
+ lid = item[:lid].to_s.presence
42
+ next unless lid
43
+
44
+ resource_class = resource_class_for(item[:type])
45
+ type_name = RelationshipHelpers.resource_type_name(resource_class)
46
+ @lid_resolver.add(lid, type: type_name, id: record.id)
47
+ end
48
+ @created_records
49
+ end
50
+
51
+ attr_reader :lid_resolver, :created_records
52
+
53
+ private
54
+
55
+ def symbolize_item(item)
56
+ item = item.to_unsafe_h if item.respond_to?(:to_unsafe_h)
57
+ ParamHelpers.deep_symbolize_params(item.to_h)
58
+ end
59
+
60
+ def create_one(item, included_index)
61
+ type = item[:type].to_s.presence
62
+ raise ArgumentError, "Included resource is missing type" unless type
63
+
64
+ resource_class = resource_class_for(type)
65
+ model_class = resource_class.model_class
66
+ params_hash, attachments = prepare_create_params_for_item(item, model_class, resource_class)
67
+
68
+ custom = try_sidepost_create(resource_class, params_hash, included_index)
69
+ return custom if custom
70
+
71
+ create_record_default(model_class, params_hash, attachments, resource_class, included_index)
72
+ end
73
+
74
+ def try_sidepost_create(resource_class, params_hash, included_index)
75
+ custom = resource_class.sidepost_create(params: params_hash, controller: @controller)
76
+ return nil unless custom
77
+
78
+ raise ValidationError.new(record: custom, included_index: included_index) unless custom.persisted?
79
+
80
+ custom
81
+ end
82
+
83
+ def create_record_default(model_class, params_hash, attachments, resource_class, included_index)
84
+ record = model_class.new(params_hash)
85
+ @controller.send(:authorize_resource_action!, record, action: :create)
86
+ @controller.send(:attach_active_storage_files, record, attachments, resource_class: resource_class)
87
+ raise ValidationError.new(record: record, included_index: included_index) unless record.save
88
+
89
+ record
90
+ end
91
+
92
+ def resource_class_for(type)
93
+ namespace = @controller.instance_variable_get(:@jsonapi_namespace)
94
+ ResourceLoader.find(type, namespace: namespace)
95
+ end
96
+
97
+ def prepare_create_params_for_item(item, model_class, _resource_class)
98
+ hash = Deserializer.new(item, model_class: model_class, action: :create).to_model_attributes
99
+ attachments = @controller.send(:extract_active_storage_params_from_hash, hash, model_class)
100
+ attachments.each_key { |k| hash.delete(k.to_s) }
101
+ hash.delete("type")
102
+ hash.delete(:type)
103
+ apply_sti_type(model_class, hash) if sti_base_with_type_column?(model_class)
104
+ [hash, attachments]
105
+ end
106
+
107
+ def apply_sti_type(klass, params)
108
+ return unless sti_base_with_type_column?(klass)
109
+
110
+ params["type"] = klass.name
111
+ end
112
+
113
+ def sti_base_with_type_column?(klass)
114
+ klass.respond_to?(:base_class) && klass.base_class == klass && klass.column_names.include?("type")
115
+ end
116
+ end
117
+ end
118
+ end
@@ -44,6 +44,10 @@ module JSONAPI
44
44
  ResourceIdentifier.extract_id(identifier)
45
45
  end
46
46
 
47
+ def extract_lid_from_identifier(identifier)
48
+ ResourceIdentifier.extract_lid(identifier)
49
+ end
50
+
47
51
  def extract_type_from_identifier(identifier)
48
52
  ResourceIdentifier.extract_type(identifier)
49
53
  end
@@ -17,6 +17,10 @@ module JSONAPI
17
17
  identifier[:id].to_s.presence
18
18
  end
19
19
 
20
+ def extract_lid(identifier)
21
+ identifier[:lid].to_s.presence
22
+ end
23
+
20
24
  def extract_type(identifier)
21
25
  identifier[:type]
22
26
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSONAPI
4
- VERSION = "2.0.2"
4
+ VERSION = "2.1.0"
5
5
  end
data/lib/json_api.rb CHANGED
@@ -11,7 +11,7 @@ end
11
11
  require "json_api/errors/parameter_not_allowed"
12
12
 
13
13
  module JSONAPI
14
- # Rebuild BaseController and RelationshipsController to reflect the current
14
+ # Rebuild BaseController, RelationshipsController, and ResourcesController to reflect the current
15
15
  # base_controller_class configuration. Safe to call repeatedly.
16
16
  def self.rebuild_base_controllers!
17
17
  remove_const(:BaseController) if const_defined?(:BaseController)
@@ -19,6 +19,9 @@ module JSONAPI
19
19
 
20
20
  remove_const(:RelationshipsController) if const_defined?(:RelationshipsController)
21
21
  load "json_api/controllers/relationships_controller.rb"
22
+
23
+ remove_const(:ResourcesController) if const_defined?(:ResourcesController)
24
+ load "json_api/controllers/resources_controller.rb"
22
25
  end
23
26
  end
24
27
 
@@ -48,6 +51,8 @@ require "json_api/support/responders"
48
51
  require "json_api/support/instrumentation"
49
52
  require "json_api/serialization/serializer"
50
53
  require "json_api/serialization/deserializer"
54
+ require "json_api/sideposting/lid_resolver"
55
+ require "json_api/sideposting/processor"
51
56
  require "json_api/controllers/concerns/controller_helpers"
52
57
  require "json_api/controllers/concerns/resource_actions"
53
58
  require "json_api/controllers/base_controller"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jpie
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.2
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emil Kampp
@@ -85,6 +85,13 @@ executables: []
85
85
  extensions: []
86
86
  extra_rdoc_files: []
87
87
  files:
88
+ - ".claude/skills/nasa-power-of-ten-ruby/SKILL.md"
89
+ - ".claude/skills/root-cause-analysis/SKILL.md"
90
+ - ".claude/skills/skills-and-subagents/SKILL.md"
91
+ - ".cursor/agents/bias-reviewer.md"
92
+ - ".cursor/agents/lint-format.md"
93
+ - ".cursor/agents/nasa-power-of-ten-reviewer.md"
94
+ - ".cursor/agents/systematic-debugging.md"
88
95
  - ".cursor/rules/release.mdc"
89
96
  - ".gitignore"
90
97
  - ".rspec"
@@ -127,6 +134,8 @@ files:
127
134
  - lib/json_api/controllers/concerns/resource_actions/pagination.rb
128
135
  - lib/json_api/controllers/concerns/resource_actions/resource_loading.rb
129
136
  - lib/json_api/controllers/concerns/resource_actions/serialization.rb
137
+ - lib/json_api/controllers/concerns/resource_actions/sideposting.rb
138
+ - lib/json_api/controllers/concerns/resource_actions/sideposting_primary_first.rb
130
139
  - lib/json_api/controllers/concerns/resource_actions/type_validation.rb
131
140
  - lib/json_api/controllers/relationships_controller.rb
132
141
  - lib/json_api/controllers/resources_controller.rb
@@ -158,6 +167,9 @@ files:
158
167
  - lib/json_api/serialization/deserializer.rb
159
168
  - lib/json_api/serialization/include_path_helpers.rb
160
169
  - lib/json_api/serialization/serializer.rb
170
+ - lib/json_api/sideposting/lid_resolver.rb
171
+ - lib/json_api/sideposting/order.rb
172
+ - lib/json_api/sideposting/processor.rb
161
173
  - lib/json_api/support/active_storage_support.rb
162
174
  - lib/json_api/support/collection_query.rb
163
175
  - lib/json_api/support/concerns/condition_building.rb