jsonapi-serializable icon indicating copy to clipboard operation
jsonapi-serializable copied to clipboard

Evaluate relationship blocks in Resource context

Open richmolj opened this issue 8 years ago • 8 comments

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).

richmolj avatar Jan 03 '17 20:01 richmolj

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 avatar Jan 04 '17 11:01 beauby

@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?

richmolj avatar Jan 04 '17 16:01 richmolj

IMHO if it's eval'd in the resource context there needs to be another variable to access the relationship object

andreaseger avatar Jan 19 '17 16:01 andreaseger

So one possible solution to your problem @richmolj would be to forward to base object upon missing method. What do you think?

beauby avatar Jul 12 '17 15:07 beauby

Sounds like a performance issue?

richmolj avatar Jul 12 '17 15:07 richmolj

I doubt it would be a bottleneck but I'll try to hack something together and we'll benchmark it.

beauby avatar Jul 12 '17 15:07 beauby

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

aprescott avatar Aug 30 '18 14:08 aprescott

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.

aprescott avatar Aug 30 '18 17:08 aprescott