jsonapi-resources 0.9.0 → 0.10.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE.txt +1 -1
  3. data/README.md +34 -11
  4. data/lib/bug_report_templates/rails_5_latest.rb +125 -0
  5. data/lib/bug_report_templates/rails_5_master.rb +140 -0
  6. data/lib/jsonapi/active_relation/adapters/join_left_active_record_adapter.rb +27 -0
  7. data/lib/jsonapi/active_relation/join_manager.rb +303 -0
  8. data/lib/jsonapi/active_relation_resource.rb +884 -0
  9. data/lib/jsonapi/acts_as_resource_controller.rb +122 -105
  10. data/lib/jsonapi/basic_resource.rb +1162 -0
  11. data/lib/jsonapi/cached_response_fragment.rb +127 -0
  12. data/lib/jsonapi/compiled_json.rb +11 -1
  13. data/lib/jsonapi/configuration.rb +71 -8
  14. data/lib/jsonapi/error.rb +27 -0
  15. data/lib/jsonapi/error_codes.rb +2 -0
  16. data/lib/jsonapi/exceptions.rb +80 -50
  17. data/lib/jsonapi/formatter.rb +3 -3
  18. data/lib/jsonapi/include_directives.rb +18 -65
  19. data/lib/jsonapi/link_builder.rb +74 -80
  20. data/lib/jsonapi/operation.rb +16 -5
  21. data/lib/jsonapi/operation_result.rb +74 -16
  22. data/lib/jsonapi/path.rb +43 -0
  23. data/lib/jsonapi/path_segment.rb +76 -0
  24. data/lib/jsonapi/processor.rb +239 -111
  25. data/lib/jsonapi/relationship.rb +153 -15
  26. data/lib/jsonapi/request_parser.rb +430 -367
  27. data/lib/jsonapi/resource.rb +3 -1253
  28. data/lib/jsonapi/resource_controller_metal.rb +5 -2
  29. data/lib/jsonapi/resource_fragment.rb +47 -0
  30. data/lib/jsonapi/resource_id_tree.rb +112 -0
  31. data/lib/jsonapi/resource_identity.rb +42 -0
  32. data/lib/jsonapi/resource_serializer.rb +143 -285
  33. data/lib/jsonapi/resource_set.rb +176 -0
  34. data/lib/jsonapi/resources/railtie.rb +9 -0
  35. data/lib/jsonapi/resources/version.rb +1 -1
  36. data/lib/jsonapi/response_document.rb +105 -83
  37. data/lib/jsonapi/routing_ext.rb +48 -26
  38. data/lib/jsonapi-resources.rb +20 -4
  39. data/lib/tasks/check_upgrade.rake +52 -0
  40. metadata +50 -20
  41. data/lib/jsonapi/cached_resource_fragment.rb +0 -127
  42. data/lib/jsonapi/operation_dispatcher.rb +0 -88
  43. data/lib/jsonapi/operation_results.rb +0 -35
  44. data/lib/jsonapi/relationship_builder.rb +0 -167
@@ -4,14 +4,12 @@ module JSONAPI
4
4
  # For example ['posts.comments.tags']
5
5
  # will transform into =>
6
6
  # {
7
- # posts:{
8
- # include:true,
9
- # include_related:{
7
+ # posts: {
8
+ # include_related: {
10
9
  # comments:{
11
- # include:true,
12
- # include_related:{
13
- # tags:{
14
- # include:true
10
+ # include_related: {
11
+ # tags: {
12
+ # include_related: {}
15
13
  # }
16
14
  # }
17
15
  # }
@@ -19,9 +17,8 @@ module JSONAPI
19
17
  # }
20
18
  # }
21
19
 
22
- def initialize(resource_klass, includes_array, force_eager_load: false)
20
+ def initialize(resource_klass, includes_array)
23
21
  @resource_klass = resource_klass
24
- @force_eager_load = force_eager_load
25
22
  @include_directives_hash = { include_related: {} }
26
23
  includes_array.each do |include|
27
24
  parse_include(include)
@@ -32,69 +29,25 @@ module JSONAPI
32
29
  @include_directives_hash
33
30
  end
34
31
 
35
- def model_includes
36
- get_includes(@include_directives_hash)
37
- end
38
-
39
- def paths
40
- delve_paths(get_includes(@include_directives_hash, false))
41
- end
42
-
43
32
  private
44
33
 
45
- def get_related(current_path)
46
- current = @include_directives_hash
47
- current_resource_klass = @resource_klass
48
- current_path.split('.').each do |fragment|
49
- fragment = fragment.to_sym
50
-
51
- if current_resource_klass
52
- current_relationship = current_resource_klass._relationships[fragment]
53
- current_resource_klass = current_relationship.try(:resource_klass)
54
- else
55
- warn "[RELATIONSHIP NOT FOUND] Relationship could not be found for #{current_path}."
56
- end
57
-
58
- include_in_join = @force_eager_load || !current_relationship || current_relationship.eager_load_on_include
59
-
60
- current[:include_related][fragment] ||= { include: false, include_related: {}, include_in_join: include_in_join }
61
- current = current[:include_related][fragment]
62
- end
63
- current
64
- end
65
-
66
- def get_includes(directive, only_joined_includes = true)
67
- ir = directive[:include_related]
68
- ir = ir.select { |k,v| v[:include_in_join] } if only_joined_includes
34
+ def parse_include(include)
35
+ path = JSONAPI::Path.new(resource_klass: @resource_klass,
36
+ path_string: include,
37
+ ensure_default_field: false,
38
+ parse_fields: false)
69
39
 
70
- ir.map do |name, sub_directive|
71
- sub = get_includes(sub_directive, only_joined_includes)
72
- sub.any? ? { name => sub } : name
73
- end
74
- end
40
+ current = @include_directives_hash
75
41
 
76
- def parse_include(include)
77
- parts = include.split('.')
78
- local_path = ''
42
+ path.segments.each do |segment|
43
+ relationship_name = segment.relationship.name.to_sym
79
44
 
80
- parts.each do |name|
81
- local_path += local_path.length > 0 ? ".#{name}" : name
82
- related = get_related(local_path)
83
- related[:include] = true
45
+ current[:include_related][relationship_name] ||= { include_related: {} }
46
+ current = current[:include_related][relationship_name]
84
47
  end
85
- end
86
48
 
87
- def delve_paths(obj)
88
- case obj
89
- when Array
90
- obj.map{|elem| delve_paths(elem)}.flatten(1)
91
- when Hash
92
- obj.map{|k,v| [[k]] + delve_paths(v).map{|path| [k] + path } }.flatten(1)
93
- when Symbol, String
94
- [[obj]]
95
- else
96
- raise "delve_paths cannot descend into #{obj.class.name}"
97
- end
49
+ rescue JSONAPI::Exceptions::InvalidRelationship => _e
50
+ raise JSONAPI::Exceptions::InvalidInclude.new(@resource_klass, include)
98
51
  end
99
52
  end
100
53
  end
@@ -3,107 +3,100 @@ module JSONAPI
3
3
  attr_reader :base_url,
4
4
  :primary_resource_klass,
5
5
  :route_formatter,
6
- :engine_name
6
+ :engine,
7
+ :engine_mount_point,
8
+ :url_helpers
9
+
10
+ @@url_helper_methods = {}
7
11
 
8
12
  def initialize(config = {})
9
- @base_url = config[:base_url]
13
+ @base_url = config[:base_url]
10
14
  @primary_resource_klass = config[:primary_resource_klass]
11
- @route_formatter = config[:route_formatter]
12
- @engine_name = build_engine_name
15
+ @route_formatter = config[:route_formatter]
16
+ @engine = build_engine
17
+ @engine_mount_point = @engine ? @engine.routes.find_script_name({}) : ""
13
18
 
14
- # Warning: These make LinkBuilder non-thread-safe. That's not a problem with the
15
- # request-specific way it's currently used, though.
16
- @resources_path_cache = JSONAPI::NaiveCache.new do |source_klass|
17
- formatted_module_path_from_class(source_klass) + format_route(source_klass._type.to_s)
18
- end
19
+ # url_helpers may be either a controller which has the route helper methods, or the application router's
20
+ # url helpers module, `Rails.application.routes.url_helpers`. Because the method no longer behaves as a
21
+ # singleton, and it's expensive to generate the module, the controller is preferred.
22
+ @url_helpers = config[:url_helpers]
19
23
  end
20
24
 
21
25
  def engine?
22
- !!@engine_name
26
+ !!@engine
23
27
  end
24
28
 
25
29
  def primary_resources_url
26
- if engine?
27
- engine_primary_resources_url
30
+ if @primary_resource_klass._routed
31
+ primary_resources_path = resources_path(primary_resource_klass)
32
+ @primary_resources_url_cached ||= "#{ base_url }#{ engine_mount_point }#{ primary_resources_path }"
28
33
  else
29
- regular_primary_resources_url
34
+ if JSONAPI.configuration.warn_on_missing_routes && !@primary_resource_klass._warned_missing_route
35
+ warn "primary_resources_url for #{@primary_resource_klass} could not be generated"
36
+ @primary_resource_klass._warned_missing_route = true
37
+ end
38
+ nil
30
39
  end
31
40
  end
32
41
 
33
42
  def query_link(query_params)
34
- "#{ primary_resources_url }?#{ query_params.to_query }"
43
+ url = primary_resources_url
44
+ return url if url.nil?
45
+ "#{ url }?#{ query_params.to_query }"
35
46
  end
36
47
 
37
48
  def relationships_related_link(source, relationship, query_params = {})
38
- url = "#{ self_link(source) }/#{ route_for_relationship(relationship) }"
39
- url = "#{ url }?#{ query_params.to_query }" if query_params.present?
40
- url
49
+ if relationship._routed
50
+ url = "#{ self_link(source) }/#{ route_for_relationship(relationship) }"
51
+ url = "#{ url }?#{ query_params.to_query }" if query_params.present?
52
+ url
53
+ else
54
+ if JSONAPI.configuration.warn_on_missing_routes && !relationship._warned_missing_route
55
+ warn "related_link for #{relationship} could not be generated"
56
+ relationship._warned_missing_route = true
57
+ end
58
+ nil
59
+ end
41
60
  end
42
61
 
43
62
  def relationships_self_link(source, relationship)
44
- "#{ self_link(source) }/relationships/#{ route_for_relationship(relationship) }"
63
+ if relationship._routed
64
+ "#{ self_link(source) }/relationships/#{ route_for_relationship(relationship) }"
65
+ else
66
+ if JSONAPI.configuration.warn_on_missing_routes && !relationship._warned_missing_route
67
+ warn "self_link for #{relationship} could not be generated"
68
+ relationship._warned_missing_route = true
69
+ end
70
+ nil
71
+ end
45
72
  end
46
73
 
47
74
  def self_link(source)
48
- if engine?
49
- engine_resource_url(source)
75
+ if source.class._routed
76
+ resource_url(source)
50
77
  else
51
- regular_resource_url(source)
78
+ if JSONAPI.configuration.warn_on_missing_routes && !source.class._warned_missing_route
79
+ warn "self_link for #{source.class} could not be generated"
80
+ source.class._warned_missing_route = true
81
+ end
82
+ nil
52
83
  end
53
84
  end
54
85
 
55
86
  private
56
87
 
57
- def build_engine_name
88
+ def build_engine
58
89
  scopes = module_scopes_from_class(primary_resource_klass)
59
90
 
60
91
  begin
61
92
  unless scopes.empty?
62
93
  "#{ scopes.first.to_s.camelize }::Engine".safe_constantize
63
94
  end
64
- rescue LoadError => e
65
- nil
66
- end
67
- end
68
-
69
- def engine_path_from_resource_class(klass)
70
- path_name = engine_resources_path_name_from_class(klass)
71
- engine_name.routes.url_helpers.public_send(path_name)
72
- end
73
95
 
74
- def engine_primary_resources_path
75
- engine_path_from_resource_class(primary_resource_klass)
76
- end
77
-
78
- def engine_primary_resources_url
79
- "#{ base_url }#{ engine_primary_resources_path }"
80
- end
81
-
82
- def engine_resource_path(source)
83
- resource_path_name = engine_resource_path_name_from_source(source)
84
- engine_name.routes.url_helpers.public_send(resource_path_name, source.id)
85
- end
86
-
87
- def engine_resource_path_name_from_source(source)
88
- scopes = module_scopes_from_class(source.class)[1..-1]
89
- base_path_name = scopes.map { |scope| scope.underscore }.join("_")
90
- end_path_name = source.class._type.to_s.singularize
91
- [base_path_name, end_path_name, "path"].reject(&:blank?).join("_")
92
- end
93
-
94
- def engine_resource_url(source)
95
- "#{ base_url }#{ engine_resource_path(source) }"
96
- end
97
-
98
- def engine_resources_path_name_from_class(klass)
99
- scopes = module_scopes_from_class(klass)[1..-1]
100
- base_path_name = scopes.map { |scope| scope.underscore }.join("_")
101
- end_path_name = klass._type.to_s
102
-
103
- if base_path_name.blank?
104
- "#{ end_path_name }_path"
105
- else
106
- "#{ base_path_name }_#{ end_path_name }_path"
96
+ # :nocov:
97
+ rescue LoadError => _e
98
+ nil
99
+ # :nocov:
107
100
  end
108
101
  end
109
102
 
@@ -112,10 +105,14 @@ module JSONAPI
112
105
  end
113
106
 
114
107
  def formatted_module_path_from_class(klass)
115
- scopes = module_scopes_from_class(klass)
108
+ scopes = if @engine
109
+ module_scopes_from_class(klass)[1..-1]
110
+ else
111
+ module_scopes_from_class(klass)
112
+ end
116
113
 
117
114
  unless scopes.empty?
118
- "/#{ scopes.map{ |scope| format_route(scope.to_s.underscore) }.join('/') }/"
115
+ "/#{ scopes.map {|scope| format_route(scope.to_s.underscore)}.compact.join('/') }/"
119
116
  else
120
117
  "/"
121
118
  end
@@ -125,24 +122,21 @@ module JSONAPI
125
122
  klass.name.to_s.split("::")[0...-1]
126
123
  end
127
124
 
128
- def regular_resources_path(source_klass)
129
- @resources_path_cache.get(source_klass)
130
- end
131
-
132
- def regular_primary_resources_path
133
- regular_resources_path(primary_resource_klass)
125
+ def resources_path(source_klass)
126
+ @_resources_path ||= {}
127
+ @_resources_path[source_klass] ||= formatted_module_path_from_class(source_klass) + format_route(source_klass._type.to_s)
134
128
  end
135
129
 
136
- def regular_primary_resources_url
137
- "#{ base_url }#{ regular_primary_resources_path }"
138
- end
139
-
140
- def regular_resource_path(source)
141
- "#{regular_resources_path(source.class)}/#{source.id}"
130
+ def resource_path(source)
131
+ if source.class.singleton?
132
+ resources_path(source.class)
133
+ else
134
+ "#{resources_path(source.class)}/#{source.id}"
135
+ end
142
136
  end
143
137
 
144
- def regular_resource_url(source)
145
- "#{ base_url }#{ regular_resource_path(source) }"
138
+ def resource_url(source)
139
+ "#{ base_url }#{ engine_mount_point }#{ resource_path(source) }"
146
140
  end
147
141
 
148
142
  def route_for_relationship(relationship)
@@ -8,17 +8,28 @@ module JSONAPI
8
8
  @options = options
9
9
  end
10
10
 
11
- def transactional?
12
- JSONAPI::Processor._processor_from_resource_type(resource_klass).transactional_operation_type?(operation_type)
13
- end
14
-
15
11
  def process
16
12
  processor.process
17
13
  end
18
14
 
19
15
  private
20
16
  def processor
21
- JSONAPI::Processor.processor_instance_for(resource_klass, operation_type, options)
17
+ self.class.processor_instance_for(resource_klass, operation_type, options)
18
+ end
19
+
20
+ class << self
21
+ def processor_instance_for(resource_klass, operation_type, params)
22
+ _processor_from_resource_type(resource_klass).new(resource_klass, operation_type, params)
23
+ end
24
+
25
+ def _processor_from_resource_type(resource_klass)
26
+ processor = resource_klass.name.gsub(/Resource$/,'Processor').safe_constantize
27
+ if processor.nil?
28
+ processor = JSONAPI.configuration.default_processor_klass
29
+ end
30
+
31
+ return processor
32
+ end
22
33
  end
23
34
  end
24
35
  end
@@ -4,12 +4,18 @@ module JSONAPI
4
4
  attr_accessor :meta
5
5
  attr_accessor :links
6
6
  attr_accessor :options
7
+ attr_accessor :warnings
7
8
 
8
9
  def initialize(code, options = {})
9
- @code = code
10
+ @code = Rack::Utils.status_code(code)
10
11
  @options = options
11
12
  @meta = options.fetch(:meta, {})
12
13
  @links = options.fetch(:links, {})
14
+ @warnings = options.fetch(:warnings, {})
15
+ end
16
+
17
+ def to_hash(serializer = nil)
18
+ {}
13
19
  end
14
20
  end
15
21
 
@@ -20,46 +26,98 @@ module JSONAPI
20
26
  @errors = errors
21
27
  super(code, options)
22
28
  end
29
+
30
+ def to_hash(serializer = nil)
31
+ {
32
+ errors: errors.collect do |error|
33
+ # :nocov:
34
+ error.to_hash
35
+ # :nocov:
36
+ end
37
+ }
38
+ end
23
39
  end
24
40
 
25
- class ResourceOperationResult < OperationResult
26
- attr_accessor :resource
41
+ class ResourceSetOperationResult < OperationResult
42
+ attr_accessor :resource_set, :pagination_params
27
43
 
28
- def initialize(code, resource, options = {})
29
- @resource = resource
44
+ def initialize(code, resource_set, options = {})
45
+ @resource_set = resource_set
46
+ @pagination_params = options.fetch(:pagination_params, {})
30
47
  super(code, options)
31
48
  end
49
+
50
+ def to_hash(serializer)
51
+ if serializer
52
+ serializer.serialize_resource_set_to_hash_single(resource_set)
53
+ else
54
+ # :nocov:
55
+ {}
56
+ # :nocov:
57
+ end
58
+ end
32
59
  end
33
60
 
34
- class ResourcesOperationResult < OperationResult
35
- attr_accessor :resources, :pagination_params, :record_count, :page_count
61
+ class ResourcesSetOperationResult < OperationResult
62
+ attr_accessor :resource_set, :pagination_params, :record_count, :page_count
36
63
 
37
- def initialize(code, resources, options = {})
38
- @resources = resources
64
+ def initialize(code, resource_set, options = {})
65
+ @resource_set = resource_set
39
66
  @pagination_params = options.fetch(:pagination_params, {})
40
67
  @record_count = options[:record_count]
41
68
  @page_count = options[:page_count]
42
69
  super(code, options)
43
70
  end
71
+
72
+ def to_hash(serializer)
73
+ if serializer
74
+ serializer.serialize_resource_set_to_hash_plural(resource_set)
75
+ else
76
+ # :nocov:
77
+ {}
78
+ # :nocov:
79
+ end
80
+ end
44
81
  end
45
82
 
46
- class RelatedResourcesOperationResult < ResourcesOperationResult
47
- attr_accessor :source_resource, :_type
83
+ class RelatedResourcesSetOperationResult < ResourcesSetOperationResult
84
+ attr_accessor :resource_set, :source_resource, :_type
48
85
 
49
- def initialize(code, source_resource, type, resources, options = {})
86
+ def initialize(code, source_resource, type, resource_set, options = {})
50
87
  @source_resource = source_resource
51
88
  @_type = type
52
- super(code, resources, options)
89
+ super(code, resource_set, options)
90
+ end
91
+
92
+ def to_hash(serializer = nil)
93
+ if serializer
94
+ serializer.serialize_related_resource_set_to_hash_plural(resource_set, source_resource)
95
+ else
96
+ # :nocov:
97
+ {}
98
+ # :nocov:
99
+ end
53
100
  end
54
101
  end
55
102
 
56
- class LinksObjectOperationResult < OperationResult
57
- attr_accessor :parent_resource, :relationship
103
+ class RelationshipOperationResult < OperationResult
104
+ attr_accessor :parent_resource, :relationship, :resource_ids
58
105
 
59
- def initialize(code, parent_resource, relationship, options = {})
106
+ def initialize(code, parent_resource, relationship, resource_ids, options = {})
60
107
  @parent_resource = parent_resource
61
108
  @relationship = relationship
109
+ @resource_ids = resource_ids
62
110
  super(code, options)
63
111
  end
112
+
113
+ def to_hash(serializer = nil)
114
+ if serializer
115
+ serializer.serialize_to_relationship_hash(parent_resource, relationship, resource_ids)
116
+ else
117
+ # :nocov:
118
+ {}
119
+ # :nocov:
120
+ end
121
+ end
64
122
  end
65
123
  end
@@ -0,0 +1,43 @@
1
+ module JSONAPI
2
+ class Path
3
+ attr_reader :segments, :resource_klass
4
+ def initialize(resource_klass:,
5
+ path_string:,
6
+ ensure_default_field: true,
7
+ parse_fields: true)
8
+ @resource_klass = resource_klass
9
+
10
+ current_resource_klass = resource_klass
11
+ @segments = path_string.to_s.split('.').collect do |segment_string|
12
+ segment = PathSegment.parse(source_resource_klass: current_resource_klass,
13
+ segment_string: segment_string,
14
+ parse_fields: parse_fields)
15
+
16
+ current_resource_klass = segment.resource_klass
17
+ segment
18
+ end
19
+
20
+ if ensure_default_field && parse_fields && @segments.last.is_a?(PathSegment::Relationship)
21
+ last = @segments.last
22
+ @segments << PathSegment::Field.new(resource_klass: last.resource_klass,
23
+ field_name: last.resource_klass._primary_key)
24
+ end
25
+ end
26
+
27
+ def relationship_segments
28
+ @segments.select {|p| p.is_a?(PathSegment::Relationship)}
29
+ end
30
+
31
+ def relationship_path_string
32
+ relationship_segments.collect(&:to_s).join('.')
33
+ end
34
+
35
+ def last_relationship
36
+ if @segments.last.is_a?(PathSegment::Relationship)
37
+ @segments.last
38
+ else
39
+ @segments[-2]
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,76 @@
1
+ module JSONAPI
2
+ class PathSegment
3
+ def self.parse(source_resource_klass:, segment_string:, parse_fields: true)
4
+ first_part, last_part = segment_string.split('#', 2)
5
+ relationship = source_resource_klass._relationship(first_part)
6
+
7
+ if relationship
8
+ if last_part
9
+ unless relationship.resource_types.include?(last_part)
10
+ raise JSONAPI::Exceptions::InvalidRelationship.new(source_resource_klass._type, segment_string)
11
+ end
12
+ resource_klass = source_resource_klass.resource_klass_for(last_part)
13
+ end
14
+ return PathSegment::Relationship.new(relationship: relationship, resource_klass: resource_klass)
15
+ else
16
+ if last_part.blank? && parse_fields
17
+ return PathSegment::Field.new(resource_klass: source_resource_klass, field_name: first_part)
18
+ else
19
+ raise JSONAPI::Exceptions::InvalidRelationship.new(source_resource_klass._type, segment_string)
20
+ end
21
+ end
22
+ end
23
+
24
+ class Relationship
25
+ attr_reader :relationship, :resource_klass
26
+
27
+ def initialize(relationship:, resource_klass: nil)
28
+ @relationship = relationship
29
+ @resource_klass = resource_klass
30
+ end
31
+
32
+ def eql?(other)
33
+ other.is_a?(JSONAPI::PathSegment::Relationship) && relationship == other.relationship && resource_klass == other.resource_klass
34
+ end
35
+
36
+ def hash
37
+ [relationship, resource_klass].hash
38
+ end
39
+
40
+ def to_s
41
+ @resource_klass ? "#{relationship.parent_resource_klass._type}.#{relationship.name}##{resource_klass._type}" : "#{resource_klass._type}.#{relationship.name}"
42
+ end
43
+
44
+ def resource_klass
45
+ @resource_klass || relationship.resource_klass
46
+ end
47
+
48
+ def path_specified_resource_klass?
49
+ !@resource_klass.nil?
50
+ end
51
+ end
52
+
53
+ class Field
54
+ attr_reader :resource_klass, :field_name
55
+
56
+ def initialize(resource_klass:, field_name:)
57
+ @resource_klass = resource_klass
58
+ @field_name = field_name
59
+ end
60
+
61
+ def eql?(other)
62
+ other.is_a?(JSONAPI::PathSegment::Field) && field_name == other.field_name && resource_klass == other.resource_klass
63
+ end
64
+
65
+ def delegated_field_name
66
+ resource_klass._attribute_delegated_name(field_name)
67
+ end
68
+
69
+ def to_s
70
+ # :nocov:
71
+ "#{resource_klass._type}.#{field_name.to_s}"
72
+ # :nocov:
73
+ end
74
+ end
75
+ end
76
+ end