Evaluate relationship blocks in Resource context
Consider a serializer:
class SerializableEmployee < JSONAPI::Serializable::Resource
attribute :title do
primary_office.title
end
has_one :primary_office do
data { primary_office }
end
private
def primary_office
@object.offices.sort_by(&:rank).first
end
end
The above code will not work, because the has_one data block will eval in the context of a Relationship instance (ie self is_a JSONAPI::Serializable::Relationship). This means I cannot access other methods within the serializable (ie primary_office).
It's confusing, as we would have access to @model and other blocks (like the attribute block above), do eval in the context of the serializable.
I'd prefer this logic live in the serializable, not the model, as it's serialization-specific (For instance, a primary_office method on the resource may fire a query instead of sorting already-loaded objects).
You are right. The way things work currently is that relationship blocks are evald in a specific context in which exposures are forwarded.
A quick workaround for your situation would be to move the method definition inside the has_one block.
I am not sure what the best tradeoff is here.
@beauby the method needs to be shared with the rest of the serializable, though, so I'm not sure that's a DRY solution. Is there a downside to evaling these blocks in the serializable context?
IMHO if it's eval'd in the resource context there needs to be another variable to access the relationship object
So one possible solution to your problem @richmolj would be to forward to base object upon missing method. What do you think?
Sounds like a performance issue?
I doubt it would be a bottleneck but I'll try to hack something together and we'll benchmark it.
I'm experiencing this same problem with a serializer I'm trying to define. I'm not too familiar with the jsonapi-rb internals, but I have a couple of ideas for tackling this.
If method_missing is too much of a concern, one idea could be to provide a way of accessing the resource object from within the relationship context.
attribute :title do
primary_office.title
end
has_one :primary_office do
# `resource` could instead be `@resource`, but that would conflict with exposures
data { resource.primary_office }
end
private
def primary_office
@object.offices.sort_by(&:rank).first
end
Another idea might be to fully scope helper methods within a defined abstraction. That would force attribute and relationships to use the same interface:
attribute :title do
helpers.primary_office.title
end
has_one :primary_office do
data { helpers.primary_office }
end
helpers do
def primary_office
@object.offices.sort_by(&:rank).first
end
end
Maybe an arbitrary def foo outside of helpers (at the same level as attribute do ... end) is unavailable within blocks in this case?
Depending on how complicated this gets with all the overlapping contexts and generic instance variables, methods defined in helpers could be required to take parameters to enforce structure:
attribute :title do
helpers.primary_office(@object)
end
helpers do
def primary_office(employee)
employee.offices.sort_by(&:rank).first
end
end
Related to this problem: link has the same issue:
link :related do
# unable to reference methods defined at the serializer level
end
I'm not sure there's a work-around either. def foo won't quite work the same way because there's no nested DSL form for link.