graphiti-activegraph 1.3.0 → 1.3.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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/specs.yml +58 -51
  3. data/.gitignore +59 -59
  4. data/.hound.yml +4 -0
  5. data/.rspec +1 -1
  6. data/.rubocop.yml +3 -0
  7. data/CHANGELOG.md +58 -50
  8. data/CHANGELOG_PRE_1.0.0.md +70 -70
  9. data/Gemfile +3 -3
  10. data/LICENSE.txt +21 -21
  11. data/README.md +130 -123
  12. data/docs/deserializer.md +40 -0
  13. data/graphiti-activegraph.gemspec +34 -32
  14. data/lib/graphiti/active_graph/adapters/active_graph/function_sideload.rb +7 -7
  15. data/lib/graphiti/active_graph/adapters/active_graph/has_many_sideload.rb +7 -7
  16. data/lib/graphiti/active_graph/adapters/active_graph/has_one_sideload.rb +7 -7
  17. data/lib/graphiti/active_graph/adapters/active_graph/polymorphic_belongs_to.rb +11 -11
  18. data/lib/graphiti/active_graph/adapters/active_graph/sideload.rb +26 -26
  19. data/lib/graphiti/active_graph/adapters/active_graph.rb +183 -183
  20. data/lib/graphiti/active_graph/concerns/path_relationships.rb +44 -44
  21. data/lib/graphiti/active_graph/concerns/relationships.rb +15 -0
  22. data/lib/graphiti/active_graph/deserializer.rb +138 -141
  23. data/lib/graphiti/active_graph/extensions/context.rb +17 -17
  24. data/lib/graphiti/active_graph/extensions/grouping/params.rb +101 -52
  25. data/lib/graphiti/active_graph/extensions/query_dsl/performer.rb +38 -0
  26. data/lib/graphiti/active_graph/extensions/query_dsl/query_generator.rb +20 -0
  27. data/lib/graphiti/active_graph/extensions/query_params.rb +27 -27
  28. data/lib/graphiti/active_graph/extensions/resources/authorizationable.rb +29 -29
  29. data/lib/graphiti/active_graph/extensions/resources/payload_combinable.rb +24 -24
  30. data/lib/graphiti/active_graph/extensions/resources/preloadable.rb +19 -19
  31. data/lib/graphiti/active_graph/extensions/resources/rel.rb +19 -19
  32. data/lib/graphiti/active_graph/jsonapi_ext/include_directive.rb +66 -66
  33. data/lib/graphiti/active_graph/jsonapi_ext/serializable/resource_ext.rb +8 -8
  34. data/lib/graphiti/active_graph/query.rb +76 -76
  35. data/lib/graphiti/active_graph/request_validators/validator.rb +9 -9
  36. data/lib/graphiti/active_graph/resource.rb +103 -103
  37. data/lib/graphiti/active_graph/resource_proxy.rb +86 -86
  38. data/lib/graphiti/active_graph/resources/interface.rb +14 -14
  39. data/lib/graphiti/active_graph/resources/persistence.rb +25 -25
  40. data/lib/graphiti/active_graph/runner.rb +39 -39
  41. data/lib/graphiti/active_graph/scope.rb +28 -28
  42. data/lib/graphiti/active_graph/scoping/association_eager_load.rb +34 -34
  43. data/lib/graphiti/active_graph/scoping/filter.rb +49 -40
  44. data/lib/graphiti/active_graph/scoping/filterable.rb +12 -12
  45. data/lib/graphiti/active_graph/scoping/include.rb +48 -48
  46. data/lib/graphiti/active_graph/scoping/internal/extra_field_normalizer.rb +76 -76
  47. data/lib/graphiti/active_graph/scoping/internal/include_normalizer.rb +82 -82
  48. data/lib/graphiti/active_graph/scoping/internal/path_descriptor.rb +94 -94
  49. data/lib/graphiti/active_graph/scoping/internal/sort_normalizer.rb +54 -54
  50. data/lib/graphiti/active_graph/scoping/internal/sorting_aliases.rb +35 -35
  51. data/lib/graphiti/active_graph/scoping/internal/sparse_fields_eagerloading.rb +28 -28
  52. data/lib/graphiti/active_graph/serializer.rb +15 -15
  53. data/lib/graphiti/active_graph/sideload_resolve.rb +119 -119
  54. data/lib/graphiti/active_graph/util/parsers/rel_chain.rb +27 -27
  55. data/lib/graphiti/active_graph/util/relationship_payload.rb +33 -33
  56. data/lib/graphiti/active_graph/util/serializer_attribute.rb +17 -17
  57. data/lib/graphiti/active_graph/util/serializer_relationship.rb +28 -28
  58. data/lib/graphiti/active_graph/util/transformers/relation_param.rb +56 -56
  59. data/lib/graphiti/active_graph/version.rb +5 -5
  60. data/lib/graphiti/sidepost_configuration.rb +9 -9
  61. data/lib/graphiti-activegraph.rb +43 -43
  62. metadata +37 -3
@@ -1,141 +1,138 @@
1
- module Graphiti::ActiveGraph
2
- class Deserializer < Graphiti::Deserializer
3
- include Concerns::PathRelationships
4
-
5
- class Conflict < StandardError
6
- attr_reader :key, :path_value, :body_value
7
-
8
- def initialize(key, path_value, body_value)
9
- @key = key
10
- @path_value = path_value
11
- @body_value = body_value
12
- end
13
-
14
- def message
15
- "Path parameter #{key} with value '#{path_value}' conflicts with payload value '#{body_value}'"
16
- end
17
- end
18
-
19
- def initialize(payload, env = nil, model = nil, parent_map = nil)
20
- super(payload)
21
-
22
- @params = payload
23
- @model = model
24
- @parent_map = parent_map || {}
25
- @env = env
26
-
27
- return unless data.blank? && env && parsable_content?
28
-
29
- raise ArgumentError, "JSON API payload must contain the 'data' key"
30
- end
31
-
32
- def process_relationship_datum(datum)
33
- {
34
- meta: {
35
- jsonapi_type: datum[:type],
36
- temp_id: datum[:'temp-id'],
37
- method: datum[:method]&.to_sym
38
- },
39
- attributes: datum[:id] ? { id: datum[:id] } : {},
40
- relationships: {}
41
- }
42
- end
43
-
44
- def meta_params
45
- data[:meta] || {}
46
- end
47
-
48
- def process_relationships(relationship_hash)
49
- {}.tap do |hash|
50
- relationship_hash.each_pair do |name, relationship_payload|
51
- name = name.to_sym
52
- data_payload = relationship_payload[:data]
53
- hash[name] = data_payload.nil? ? process_nil_relationship(name) : process_relationship(relationship_payload[:data])
54
- end
55
- end
56
- end
57
-
58
- def relationship?(name)
59
- relationships[name.to_sym].present?
60
- end
61
-
62
- # change empty relationship as `disassociate` hash so they will be removed
63
- def process_nil_relationship(name)
64
- attributes = {}
65
- method_name = :disassociate
66
-
67
- {
68
- meta: {
69
- jsonapi_type: name.to_sym,
70
- method: method_name
71
- },
72
- attributes: attributes,
73
- relationships: {}
74
- }
75
- end
76
-
77
- def meta(action: nil)
78
- results = super
79
- return results if action.present? || @env.nil?
80
-
81
- action = case @env['REQUEST_METHOD']
82
- when 'POST' then :create
83
- when 'PUT', 'PATCH' then :update
84
- when 'DELETE' then :destroy
85
- end
86
-
87
- results[:method] = action
88
- results
89
- end
90
-
91
- def path_map
92
- map = @params.select { |key, _| key =~ /_id$/ }.permit!.to_h
93
- map = filter_keys(map) { |key| key.gsub(/_id$/, '').to_sym }
94
- map = filter_keys(map) { |key| @parent_map[key] || key }
95
- map = filter_keys_presence(map) if @model < ActiveGraph::Node
96
- map
97
- end
98
-
99
- def filter_keys_presence(map)
100
- filter_keys(map) { |key| presence(key) || presence(key.to_s.pluralize.to_sym) }
101
- end
102
-
103
- def filter_keys(map)
104
- map.map { |key, v| [yield(key), v] }.select(&:first).to_h
105
- end
106
-
107
- def presence(key)
108
- key if @model.associations.include?(key)
109
- end
110
-
111
- def detect_conflict(key, path_value, body_value)
112
- raise Conflict.new(key, path_value, body_value) if path_value && body_value && body_value != path_value
113
- end
114
-
115
- private
116
-
117
- def parsable_content?
118
- true
119
- end
120
-
121
- def derive_resource_type(rel_name)
122
- if @model.include?(ActiveGraph::Node)
123
- @model.associations[rel_name].target_class.model_name.plural.to_s
124
- else
125
- # ApplicationRelationship resource doesn't have #associations or any other method to list all associations
126
- # "from_class" and "to_class" methods can contain value "any". Which makes it unreliable to use here
127
- # Using "rel_name.to_s.pluralize" as it works in most cases.
128
- # User can define a method on controller "" which will override this behaviour
129
- derive_resource_type_from_controller(rel_name) || rel_name.to_s.pluralize
130
- end
131
- end
132
-
133
- def derive_resource_type_from_controller(rel_name)
134
- controller_obj.derive_parent_resource_type(rel_name) if controller_obj.respond_to?(:derive_parent_resource_type)
135
- end
136
-
137
- def controller_obj
138
- @controller_obj ||= Graphiti.context[:object]
139
- end
140
- end
141
- end
1
+ module Graphiti::ActiveGraph
2
+ class Deserializer < Graphiti::Deserializer
3
+ include Concerns::PathRelationships
4
+ include Concerns::Relationships
5
+
6
+ class Conflict < StandardError
7
+ attr_reader :key, :path_value, :body_value
8
+
9
+ def initialize(key, path_value, body_value)
10
+ @key = key
11
+ @path_value = path_value
12
+ @body_value = body_value
13
+ end
14
+
15
+ def message
16
+ "Path parameter #{key} with value '#{path_value}' conflicts with payload value '#{body_value}'"
17
+ end
18
+ end
19
+
20
+ def initialize(payload, env = nil, model = nil, parent_map = nil)
21
+ super(payload)
22
+
23
+ @params = payload
24
+ @model = model
25
+ @parent_map = parent_map || {}
26
+ @env = env
27
+
28
+ return unless data.blank? && env && parsable_content?
29
+
30
+ raise ArgumentError, "JSON API payload must contain the 'data' key"
31
+ end
32
+
33
+ def process_relationship_datum(datum)
34
+ {
35
+ meta: {
36
+ jsonapi_type: datum[:type],
37
+ temp_id: datum[:'temp-id'],
38
+ method: datum[:method]&.to_sym
39
+ },
40
+ attributes: datum[:id] ? { id: datum[:id] } : {},
41
+ relationships: {}
42
+ }
43
+ end
44
+
45
+ def meta_params
46
+ data[:meta] || {}
47
+ end
48
+
49
+ def process_relationships(relationship_hash)
50
+ {}.tap do |hash|
51
+ relationship_hash.each_pair do |name, relationship_payload|
52
+ name = name.to_sym
53
+ data_payload = relationship_payload[:data]
54
+ hash[name] = data_payload.nil? ? process_nil_relationship(name) : process_relationship(relationship_payload[:data])
55
+ end
56
+ end
57
+ end
58
+
59
+ # change empty relationship as `disassociate` hash so they will be removed
60
+ def process_nil_relationship(name)
61
+ attributes = {}
62
+ method_name = :disassociate
63
+
64
+ {
65
+ meta: {
66
+ jsonapi_type: name.to_sym,
67
+ method: method_name
68
+ },
69
+ attributes: attributes,
70
+ relationships: {}
71
+ }
72
+ end
73
+
74
+ def meta(action: nil)
75
+ results = super
76
+ return results if action.present? || @env.nil?
77
+
78
+ action = case @env['REQUEST_METHOD']
79
+ when 'POST' then :create
80
+ when 'PUT', 'PATCH' then :update
81
+ when 'DELETE' then :destroy
82
+ end
83
+
84
+ results[:method] = action
85
+ results
86
+ end
87
+
88
+ def path_map
89
+ map = @params.select { |key, _| key =~ /_id$/ }.permit!.to_h
90
+ map = filter_keys(map) { |key| key.gsub(/_id$/, '').to_sym }
91
+ map = filter_keys(map) { |key| @parent_map[key] || key }
92
+ map = filter_keys_presence(map) if @model < ActiveGraph::Node
93
+ map
94
+ end
95
+
96
+ def filter_keys_presence(map)
97
+ filter_keys(map) { |key| presence(key) || presence(key.to_s.pluralize.to_sym) }
98
+ end
99
+
100
+ def filter_keys(map)
101
+ map.map { |key, v| [yield(key), v] }.select(&:first).to_h
102
+ end
103
+
104
+ def presence(key)
105
+ key if @model.associations.include?(key)
106
+ end
107
+
108
+ def detect_conflict(key, path_value, body_value)
109
+ raise Conflict.new(key, path_value, body_value) if path_value && body_value && body_value != path_value
110
+ end
111
+
112
+ private
113
+
114
+ def parsable_content?
115
+ true
116
+ end
117
+
118
+ def derive_resource_type(rel_name)
119
+ if @model.include?(ActiveGraph::Node)
120
+ @model.associations[rel_name].target_class.model_name.plural.to_s
121
+ else
122
+ # ApplicationRelationship resource doesn't have #associations or any other method to list all associations
123
+ # "from_class" and "to_class" methods can contain value "any". Which makes it unreliable to use here
124
+ # Using "rel_name.to_s.pluralize" as it works in most cases.
125
+ # User can define a method on controller "" which will override this behaviour
126
+ derive_resource_type_from_controller(rel_name) || rel_name.to_s.pluralize
127
+ end
128
+ end
129
+
130
+ def derive_resource_type_from_controller(rel_name)
131
+ controller_obj.derive_parent_resource_type(rel_name) if controller_obj.respond_to?(:derive_parent_resource_type)
132
+ end
133
+
134
+ def controller_obj
135
+ @controller_obj ||= Graphiti.context[:object]
136
+ end
137
+ end
138
+ end
@@ -1,17 +1,17 @@
1
- module Graphiti::ActiveGraph::Extensions
2
- require 'ostruct' if RUBY_PLATFORM != 'java'
3
-
4
- module Context
5
- extend ActiveSupport::Concern
6
-
7
- class_methods do
8
- def context
9
- Thread.current.thread_variable_get(:context) || {}.tap(&method(:context=))
10
- end
11
-
12
- def context=(val)
13
- Thread.current.thread_variable_set(:context, val)
14
- end
15
- end
16
- end
17
- end
1
+ module Graphiti::ActiveGraph::Extensions
2
+ require 'ostruct' if RUBY_PLATFORM != 'java'
3
+
4
+ module Context
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ def context
9
+ Thread.current.thread_variable_get(:context) || {}.tap(&method(:context=))
10
+ end
11
+
12
+ def context=(val)
13
+ Thread.current.thread_variable_set(:context, val)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,52 +1,101 @@
1
- module Graphiti::ActiveGraph::Extensions::Grouping
2
- class Params
3
- attr_reader :params, :grouping_criteria_list, :resource_class
4
- def initialize(params, resource_class)
5
- @params = params
6
- @grouping_criteria_list = params.fetch(:group_by, nil)&.split(',') || []
7
- @resource_class = resource_class
8
- end
9
-
10
- def single_grouping_criteria?
11
- grouping_criteria_list.size < 2
12
- end
13
-
14
- def grouping_criteria_on_attribute?
15
- grouping_criteria_list.any? { |criteria| ends_with_attribute?(resource_class.model, criteria) }
16
- end
17
-
18
- def empty?
19
- grouping_criteria_list.empty?
20
- end
21
-
22
- def ends_with_attribute?(model, criteria)
23
- return false if criteria.blank?
24
-
25
- last_segment_attribute?(model, criteria.split('.'))
26
- end
27
-
28
- private
29
-
30
- def last_segment_attribute?(model, segments)
31
- last_segment = segments.last
32
- intermediate_model = traverse_to_last_associated_model(model, segments[0...-1])
33
-
34
- intermediate_model && attribute?(intermediate_model, last_segment)
35
- end
36
-
37
- def traverse_to_last_associated_model(model, intermediate_segments)
38
- intermediate_segments.each do |segment|
39
- return false unless(model = associated_model(model, segment))
40
- end
41
- model
42
- end
43
-
44
- def attribute?(model, segment)
45
- model.attribute_names.include?(segment)
46
- end
47
-
48
- def associated_model(model, segment)
49
- model.associations[segment.to_sym]&.target_class
50
- end
51
- end
52
- end
1
+ module Graphiti::ActiveGraph::Extensions::Grouping
2
+ class Params
3
+ attr_reader :params, :grouping_criteria_list, :resource_class
4
+
5
+ def initialize(params, resource_class)
6
+ @params = params
7
+ group_by_string = params.fetch(:group_by, nil)
8
+ @grouping_criteria_list = split_grouping_criteria(group_by_string)
9
+ @resource_class = resource_class
10
+ end
11
+
12
+ def single_grouping_criteria?
13
+ grouping_criteria_list.size < 2
14
+ end
15
+
16
+ def grouping_criteria_on_attribute?
17
+ grouping_criteria_list.any? { |criteria| ends_with_attribute?(resource_class.model, criteria) }
18
+ end
19
+
20
+ def empty?
21
+ grouping_criteria_list.empty?
22
+ end
23
+
24
+ def ends_with_attribute?(model, criteria)
25
+ return false if criteria.blank?
26
+
27
+ last_segment_attribute?(model, criteria.split('.'))
28
+ end
29
+
30
+ private
31
+
32
+ def last_segment_attribute?(model, segments)
33
+ last_segment = segments.last
34
+ intermediate_model = traverse_to_last_associated_model(model, segments[0...-1])
35
+
36
+ intermediate_model && attribute?(intermediate_model, last_segment)
37
+ end
38
+
39
+ def traverse_to_last_associated_model(model, intermediate_segments)
40
+ intermediate_segments.each do |segment|
41
+ return false unless (model = associated_model(model, segment))
42
+ end
43
+ model
44
+ end
45
+
46
+ def attribute?(model, segment)
47
+ model.attribute_names.include?(segment)
48
+ end
49
+
50
+ def associated_model(model, segment)
51
+ model.associations[segment.to_sym]&.target_class
52
+ end
53
+
54
+ def split_grouping_criteria(group_by_string)
55
+ return [] if group_by_string.nil? || group_by_string.empty?
56
+
57
+ result = []
58
+ current = ''
59
+ depth = 0
60
+
61
+ group_by_string.each_char do |char|
62
+ depth, current = process_char(char, depth, current, result)
63
+ end
64
+
65
+ add_criterion(result, current)
66
+ handle_mismatched_parentheses(result, depth)
67
+ end
68
+
69
+ def process_char(char, depth, current, result)
70
+ case char
71
+ when '(' then [depth + 1, current + char]
72
+ when ')' then [depth - 1, current + char]
73
+ when ',' then process_comma(depth, current, result)
74
+ else [depth, current + char]
75
+ end
76
+ end
77
+
78
+ def process_comma(depth, current, result)
79
+ if depth <= 0
80
+ add_criterion(result, current)
81
+ [depth, '']
82
+ else
83
+ [depth, current + ',']
84
+ end
85
+ end
86
+
87
+ def add_criterion(result, current)
88
+ stripped = current.strip
89
+ result << stripped unless stripped.empty?
90
+ end
91
+
92
+ def handle_mismatched_parentheses(result, final_depth)
93
+ return result if final_depth <= 0 || result.empty?
94
+
95
+ # If we ended with unclosed parentheses, re-split the last segment
96
+ last_segment = result.pop
97
+ result.concat(last_segment.split(',').map(&:strip).reject(&:empty?))
98
+ result
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,38 @@
1
+ module Graphiti::ActiveGraph::Extensions::QueryDsl
2
+ module Performer
3
+ attr_accessor :with_vars, :skip_arrow_cypher_rels
4
+
5
+ def apply_query_dsl
6
+ query_param = resource.context&.params&.[](:query)
7
+ query_param.present? ? apply_query_param(query_param) : scope
8
+ end
9
+
10
+ def apply_query_param(query_param)
11
+ @scope = query_generator.new(query_param, **query_generator_config).tap do |qg|
12
+ qg.generate_functions_optional_match
13
+ qg.generate_with_clause_partition_query
14
+ qg.generate_match_query
15
+ qg.generate_with_clause_query
16
+ end.query
17
+ end
18
+
19
+ private
20
+
21
+ def query_generator
22
+ Graphiti::ActiveGraph::Extensions::QueryDsl::QueryGenerator
23
+ end
24
+
25
+ def query_generator_config
26
+ {
27
+ query: scope,
28
+ with_vars_to_carry:,
29
+ skip_arrow_cypher_rels: skip_arrow_cypher_rels || [],
30
+ resource: resource.class
31
+ }
32
+ end
33
+
34
+ def with_vars_to_carry
35
+ (query ? with_vars_for_sort : []).push(*with_vars).push(*Graphiti.context[:with_vars]).compact
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,20 @@
1
+ module Graphiti::ActiveGraph::Extensions::QueryDsl
2
+ class QueryGenerator
3
+ def initialize(query_param, query:, **config)
4
+ @query_param = query_param
5
+ @query = query
6
+ end
7
+
8
+ def generate_functions_optional_match
9
+ end
10
+
11
+ def generate_with_clause_partition_query
12
+ end
13
+
14
+ def generate_match_query
15
+ end
16
+
17
+ def generate_with_clause_query
18
+ end
19
+ end
20
+ end
@@ -1,27 +1,27 @@
1
- module Graphiti::ActiveGraph::Extensions
2
- class QueryParams
3
- attr_reader :params, :grouping_extra_params, :resource_class
4
-
5
- def initialize(params, resource_class, grouping_extra_params: {})
6
- @params = params
7
- @resource_class = resource_class
8
- @grouping_extra_params = grouping_extra_params
9
- end
10
-
11
- def group_by
12
- Grouping::Params.new(params, resource_class)
13
- end
14
-
15
- def group_by_params
16
- group_by_params_hash unless group_by.empty?
17
- end
18
-
19
- def group_by_params_hash
20
- { group_by: group_by.grouping_criteria_list }.merge(grouping_extra_params)
21
- end
22
-
23
- def extra_field?(type, name)
24
- params.dig(:extra_fields, type)&.include?(name.to_s)
25
- end
26
- end
27
- end
1
+ module Graphiti::ActiveGraph::Extensions
2
+ class QueryParams
3
+ attr_reader :params, :grouping_extra_params, :resource_class
4
+
5
+ def initialize(params, resource_class, grouping_extra_params: {})
6
+ @params = params
7
+ @resource_class = resource_class
8
+ @grouping_extra_params = grouping_extra_params
9
+ end
10
+
11
+ def group_by
12
+ Grouping::Params.new(params, resource_class)
13
+ end
14
+
15
+ def group_by_params
16
+ group_by_params_hash unless group_by.empty?
17
+ end
18
+
19
+ def group_by_params_hash
20
+ { group_by: group_by.grouping_criteria_list }.merge(grouping_extra_params)
21
+ end
22
+
23
+ def extra_field?(type, name)
24
+ params.dig(:extra_fields, type)&.include?(name.to_s)
25
+ end
26
+ end
27
+ end
@@ -1,29 +1,29 @@
1
- module Graphiti::ActiveGraph::Extensions::Resources
2
- module Authorizationable
3
- extend ActiveSupport::Concern
4
-
5
- included do
6
- class << self
7
- attr_reader :authorize_attributes
8
- end
9
- end
10
-
11
- class_methods do
12
- def attribute_authorization
13
- @authorize_attributes = true
14
- end
15
-
16
- def authorize_attributes?
17
- @authorize_attributes
18
- end
19
-
20
- def readable_attributes
21
- attributes.select { |_attr_name, options_map| options_map[:readable] }
22
- end
23
-
24
- def readable_sideloads
25
- sideloads.select { |_sideloads_name, sideload_obj| sideload_obj.readable? }
26
- end
27
- end
28
- end
29
- end
1
+ module Graphiti::ActiveGraph::Extensions::Resources
2
+ module Authorizationable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class << self
7
+ attr_reader :authorize_attributes
8
+ end
9
+ end
10
+
11
+ class_methods do
12
+ def attribute_authorization
13
+ @authorize_attributes = true
14
+ end
15
+
16
+ def authorize_attributes?
17
+ @authorize_attributes
18
+ end
19
+
20
+ def readable_attributes
21
+ attributes.select { |_attr_name, options_map| options_map[:readable] }
22
+ end
23
+
24
+ def readable_sideloads
25
+ sideloads.select { |_sideloads_name, sideload_obj| sideload_obj.readable? }
26
+ end
27
+ end
28
+ end
29
+ end