Skip to content

Commit 729c428

Browse files
mmartinsonlgebhardt
authored andcommitted
Improve consistency and readabily of RelationshipBuilder
1 parent 83abe84 commit 729c428

2 files changed

Lines changed: 146 additions & 132 deletions

File tree

Lines changed: 133 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,152 +1,157 @@
1-
require 'pry'
2-
31
module JSONAPI
42
class RelationshipBuilder
5-
attr_reader :_model_class, :options
3+
attr_reader :model_class, :options, :relationship_class
4+
delegate :register_relationship, to: :@resource_class
65

7-
class DefinitionProxy
8-
def initialize(target)
9-
@target = target
6+
def initialize(relationship_class, model_class, options)
7+
@relationship_class = relationship_class
8+
@model_class = model_class
9+
@resource_class = options[:parent_resource]
10+
@options = options
11+
end
12+
13+
def define_relationship_methods(relationship_name)
14+
# Initialize from an ActiveRecord model's properties
15+
if model_class && model_class.ancestors.collect{|ancestor| ancestor.name}.include?('ActiveRecord::Base')
16+
model_association = model_class.reflect_on_association(relationship_name)
17+
if model_association
18+
options[:class_name] ||= model_association.class_name
19+
end
1020
end
1121

12-
def define_method(name, &body)
13-
@target.inject_class_method(name, body)
22+
relationship = register_relationship(
23+
relationship_name,
24+
relationship_class.new(relationship_name, options)
25+
)
26+
27+
foreign_key = define_foreign_key_setter(relationship.foreign_key)
28+
29+
case relationship
30+
when JSONAPI::Relationship::ToOne
31+
associated = define_resource_relationship_accessor(:one, relationship_name)
32+
args = [relationship, foreign_key, associated, relationship_name]
33+
34+
relationship.belongs_to? ? build_belongs_to(*args) : build_has_one(*args)
35+
when JSONAPI::Relationship::ToMany
36+
associated = define_resource_relationship_accessor(:many, relationship_name)
37+
38+
build_to_many(relationship, foreign_key, associated, relationship_name)
1439
end
40+
end
1541

16-
def method_defined?(name)
17-
@target.method_defined?(name)
42+
def define_foreign_key_setter(foreign_key)
43+
define_on_resource "#{foreign_key}=" do |value|
44+
@model.method("#{foreign_key}=").call(value)
1845
end
46+
foreign_key
1947
end
2048

21-
def initialize(relationship_class, options, model_class, resource_relationships, resource_klass)
22-
@relationship_class = relationship_class
23-
@options = options
24-
@_model_class = model_class
25-
@_relationships = resource_relationships
26-
@resource_klass = resource_klass
49+
def define_resource_relationship_accessor(type, relationship_name)
50+
associated_records_method_name = {
51+
one: "record_for_#{relationship_name}",
52+
many: "records_for_#{relationship_name}"
53+
}
54+
.fetch(type)
55+
56+
define_on_resource associated_records_method_name do
57+
relationship = self.class._relationships[relationship_name]
58+
relation_name = relationship.relation_name(context: @context)
59+
records_for(relation_name)
60+
end
61+
62+
associated_records_method_name
2763
end
2864

29-
#TODO change reference in code once refactor is complete
30-
def klass
31-
@relationship_class
65+
def build_belongs_to(relationship, foreign_key, associated_records_method_name, relationship_name)
66+
# Calls method matching foreign key name on model instance
67+
define_on_resource foreign_key do
68+
@model.method(foreign_key).call
69+
end
70+
71+
# Returns instantiated related resource object or nil
72+
define_on_resource relationship_name do |options = {}|
73+
relationship = self.class._relationships[relationship_name]
74+
75+
if relationship.polymorphic?
76+
associated_model = public_send(associated_records_method_name)
77+
resource_klass = self.class.resource_for_model(associated_model) if associated_model
78+
return resource_klass.new(associated_model, @context) if resource_klass
79+
else
80+
resource_klass = relationship.resource_klass
81+
if resource_klass
82+
associated_model = public_send(associated_records_method_name)
83+
return associated_model ? resource_klass.new(associated_model, @context) : nil
84+
end
85+
end
86+
end
3287
end
3388

34-
def target_resource
35-
@proxy ||= DefinitionProxy.new(@resource_klass)
89+
def build_has_one(relationship, foreign_key, associated_records_method_name, relationship_name)
90+
# Returns primary key name of related resource class
91+
define_on_resource foreign_key do
92+
relationship = self.class._relationships[relationship_name]
93+
94+
record = public_send(associated_records_method_name)
95+
return nil if record.nil?
96+
record.public_send(relationship.resource_klass._primary_key)
97+
end
98+
99+
# Returns instantiated related resource object or nil
100+
define_on_resource relationship_name do |options = {}|
101+
relationship = self.class._relationships[relationship_name]
102+
103+
resource_klass = relationship.resource_klass
104+
if resource_klass
105+
associated_model = public_send(associated_records_method_name)
106+
return associated_model ? resource_klass.new(associated_model, @context) : nil
107+
end
108+
end
36109
end
37110

38-
def build_relationship(attrs)
39-
attrs.each do |attr|
40-
relationship_name = attr.to_sym
111+
def build_to_many(relationship, foreign_key, associated_records_method_name, relationship_name)
112+
# Returns array of primary keys of related resource classes
113+
define_on_resource foreign_key do
114+
records = public_send(associated_records_method_name)
115+
return records.collect do |record|
116+
record.public_send(relationship.resource_klass._primary_key)
117+
end
118+
end
41119

42-
@resource_klass.check_reserved_relationship_name(relationship_name)
120+
# Returns array of instantiated related resource objects
121+
define_on_resource relationship_name do |options = {}|
122+
relationship = self.class._relationships[relationship_name]
43123

44-
# Initialize from an ActiveRecord model's properties
45-
if _model_class && _model_class.ancestors.collect{|ancestor| ancestor.name}.include?('ActiveRecord::Base')
46-
model_association = _model_class.reflect_on_association(relationship_name)
47-
if model_association
48-
options[:class_name] ||= model_association.class_name
49-
end
50-
end
124+
resource_klass = relationship.resource_klass
125+
records = public_send(associated_records_method_name)
51126

52-
@_relationships[relationship_name] = relationship = klass.new(relationship_name, options)
53-
54-
associated_records_method_name = case relationship
55-
when JSONAPI::Relationship::ToOne then "record_for_#{relationship_name}"
56-
when JSONAPI::Relationship::ToMany then "records_for_#{relationship_name}"
57-
end
58-
59-
foreign_key = relationship.foreign_key
60-
61-
target_resource.define_method "#{foreign_key}=" do |value|
62-
@model.method("#{foreign_key}=").call(value)
63-
end unless target_resource.method_defined?("#{foreign_key}=")
64-
65-
target_resource.define_method associated_records_method_name do
66-
relationship = self.class._relationships[relationship_name]
67-
relation_name = relationship.relation_name(context: @context)
68-
records_for(relation_name)
69-
end unless target_resource.method_defined?(associated_records_method_name)
70-
71-
if relationship.is_a?(JSONAPI::Relationship::ToOne)
72-
if relationship.belongs_to?
73-
target_resource.define_method foreign_key do
74-
@model.method(foreign_key).call
75-
end unless target_resource.method_defined?(foreign_key)
76-
77-
target_resource.define_method relationship_name do |options = {}|
78-
relationship = self.class._relationships[relationship_name]
79-
80-
if relationship.polymorphic?
81-
associated_model = public_send(associated_records_method_name)
82-
resource_klass = self.class.resource_for_model(associated_model) if associated_model
83-
return resource_klass.new(associated_model, @context) if resource_klass
84-
else
85-
resource_klass = relationship.resource_klass
86-
if resource_klass
87-
associated_model = public_send(associated_records_method_name)
88-
return associated_model ? resource_klass.new(associated_model, @context) : nil
89-
end
90-
end
91-
end unless target_resource.method_defined?(relationship_name)
92-
else
93-
target_resource.define_method foreign_key do
94-
relationship = self.class._relationships[relationship_name]
95-
96-
record = public_send(associated_records_method_name)
97-
return nil if record.nil?
98-
record.public_send(relationship.resource_klass._primary_key)
99-
end unless target_resource.method_defined?(foreign_key)
100-
101-
target_resource.define_method relationship_name do |options = {}|
102-
relationship = self.class._relationships[relationship_name]
103-
104-
resource_klass = relationship.resource_klass
105-
if resource_klass
106-
associated_model = public_send(associated_records_method_name)
107-
return associated_model ? resource_klass.new(associated_model, @context) : nil
108-
end
109-
end unless target_resource.method_defined?(relationship_name)
110-
end
111-
elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
112-
target_resource.define_method foreign_key do
113-
records = public_send(associated_records_method_name)
114-
return records.collect do |record|
115-
record.public_send(relationship.resource_klass._primary_key)
116-
end
117-
end unless target_resource.method_defined?(foreign_key)
118-
119-
target_resource.define_method relationship_name do |options = {}|
120-
relationship = self.class._relationships[relationship_name]
121-
122-
resource_klass = relationship.resource_klass
123-
records = public_send(associated_records_method_name)
124-
125-
filters = options.fetch(:filters, {})
126-
unless filters.nil? || filters.empty?
127-
records = resource_klass.apply_filters(records, filters, options)
128-
end
129-
130-
sort_criteria = options.fetch(:sort_criteria, {})
131-
unless sort_criteria.nil? || sort_criteria.empty?
132-
order_options = relationship.resource_klass.construct_order_options(sort_criteria)
133-
records = resource_klass.apply_sort(records, order_options, @context)
134-
end
135-
136-
paginator = options[:paginator]
137-
if paginator
138-
records = resource_klass.apply_pagination(records, paginator, order_options)
139-
end
140-
141-
return records.collect do |record|
142-
if relationship.polymorphic?
143-
resource_klass = self.class.resource_for_model(record)
144-
end
145-
resource_klass.new(record, @context)
146-
end
147-
end unless target_resource.method_defined?(relationship_name)
127+
filters = options.fetch(:filters, {})
128+
unless filters.nil? || filters.empty?
129+
records = resource_klass.apply_filters(records, filters, options)
130+
end
131+
132+
sort_criteria = options.fetch(:sort_criteria, {})
133+
unless sort_criteria.nil? || sort_criteria.empty?
134+
order_options = relationship.resource_klass.construct_order_options(sort_criteria)
135+
records = resource_klass.apply_sort(records, order_options, @context)
136+
end
137+
138+
paginator = options[:paginator]
139+
if paginator
140+
records = resource_klass.apply_pagination(records, paginator, order_options)
141+
end
142+
143+
return records.collect do |record|
144+
if relationship.polymorphic?
145+
resource_klass = self.class.resource_for_model(record)
148146
end
147+
resource_klass.new(record, @context)
149148
end
149+
end
150+
end
151+
152+
def define_on_resource(method_name, &block)
153+
return if @resource_class.method_defined?(method_name)
154+
@resource_class.inject_method_definition(method_name, block)
150155
end
151156
end
152157
end

lib/jsonapi/resource.rb

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -968,16 +968,25 @@ def _add_relationship(klass, *attrs)
968968
options = attrs.extract_options!
969969
options[:parent_resource] = self
970970

971-
JSONAPI::RelationshipBuilder.new(klass, options, _model_class, @_relationships, self)
972-
.build_relationship(attrs)
971+
attrs.each do |relationship_name|
972+
check_reserved_relationship_name(relationship_name)
973973

974+
JSONAPI::RelationshipBuilder.new(klass, _model_class, options)
975+
.define_relationship_methods(relationship_name.to_sym)
976+
end
974977
end
975978

976-
#added
977-
def inject_class_method(name, body)
979+
# Allows JSONAPI::RelationshipBuilder to access metaprogramming hooks
980+
def inject_method_definition(name, body)
978981
define_method(name, body)
979982
end
980983

984+
def register_relationship(name, relationship_object)
985+
@_relationships[name] = relationship_object
986+
end
987+
988+
private
989+
981990
def check_reserved_resource_name(type, name)
982991
if [:ids, :types, :hrefs, :links].include?(type)
983992
warn "[NAME COLLISION] `#{name}` is a reserved resource name."

0 commit comments

Comments
 (0)