jsonapi-resources 0.9.0 → 0.10.6

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 (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