Skip to content

Conversation

@AskarZinurov
Copy link

Hi! First of all want to thank you for your work. It is really very pleasant, light and fast lib.

In this PR I am adding abilitity to render relations only, without requirement to include associated objects later into includes array.
The relations can be rendered in mixed way, so some configured relations will be included, and some not. The default behaviour is not changed, examples of usage can be found in specs.

Will be glad to hear your feedback. Thank you!

Copy link
Owner

@mjeffrey18 mjeffrey18 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work!

Apologies for the delay.

Everything looks great, just a few DSL comments.

Plus, before we merge, let's bump the minor version and update the docs?

def initialize(@name = nil, @children = nil)
end

def embed(@embed)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use something like def include?(@include)? Feels more implicit for the desired outcome.

self
end

def have_relation?(name : Symbol)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping consistency with the rest of the codebase

Suggested change
def have_relation?(name : Symbol)
def has_relation?(name : Symbol)

end

def traverse(path)
children.not_nil!.find { |child| child.name == path }.not_nil!
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we throw an error here with a message to inform the user if they don't have child nodes?

children.nil? || children.try &.empty?
end

def traverse(path)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to find a better API for the end user. Thoughts?

Suggested change
def traverse(path)
def relationship(path) # OR find_relationship(path)

@mjeffrey18
Copy link
Owner

Hey @AskarZinurov, thanks again for your effort on this.

I tried running the benchmark script and noticed the speed has dropped a bit for both benchmarks

Main

FastJSONAPISerializer  81.62k ( 12.25µs) (± 3.10%)  22.3kB/op        fastest
    JSONApiSerializer  40.81k ( 24.51µs) (± 3.05%)  32.9kB/op   2.00× slower

Single object with 1 attribute

FastJSONAPISerializer   1.15M (869.49ns) (± 1.86%)  1.46kB/op        fastest
    JSONApiSerializer 764.79k (  1.31µs) (± 1.87%)  1.45kB/op   1.50× slower

Feature

FastJSONAPISerializer  72.02k ( 13.88µs) (± 5.04%)  24.6kB/op        fastest
    JSONApiSerializer  39.72k ( 25.17µs) (± 4.01%)  32.9kB/op   1.81× slower

Single object with 1 attribute

FastJSONAPISerializer   1.02M (983.51ns) (± 1.86%)  1.72kB/op        fastest
    JSONApiSerializer 761.17k (  1.31µs) (± 3.02%)  1.44kB/op   1.34× slower

I tried to change the new classes to structs, which helped gain some speed, but the specs are failing.

We should keep the general execution of the shard to gain this feature, although I understand there might be minor speed drops as features are added.

Another option could be to change the API.

Looking at the Ruby gem, they only use included for has_many relationships and explicitly allow included - https://github.com/jsonapi-serializer/jsonapi-serializer#compound-document
They serialize the belongs_to as relationships but skip included - https://github.com/jsonapi-serializer/jsonapi-serializer#serialized-output

Maybe that could be our approach, but using the existing setup for speed.

We could end up with something like this

class UserSerializer < FastJSONAPISerializer::Base(User)
  belongs_to :post_code, PostCodeSerializer
  belongs_to :account, serializer: AccountSerializer, key: "user_account"
  belongs_to :organisation, serializer: OrganisationSerializer, optional: true
end

Then we can assume the user serializer will always have the post and account relationship records.
But we only added organisation if its not nil and if its in the includes

Like so:

UserSerializer.new(resource).serialize
{
  "data": {
    "id": "1",
    "type": "user",
    "attributes": {
      "name": "Me"
    },
    "relationships": {
      "post_code": {
        "data": {
          "id": "101",
          "type": "post_code"
        }
      },
      "account": {
        "data": {
          "id": "101",
          "type": "account"
        }
      }
    }
  }
}
UserSerializer.new(resource).serialize(
  includes: { :organisation => [:organisation] }
)
{
  "data": {
    "id": "1",
    "type": "user",
    "attributes": {
      "name": "Me"
    },
    "relationships": {
      "post_code": {
        "data": {
          "id": "101",
          "type": "post_code"
        }
      },
      "account": {
        "data": {
          "id": "101",
          "type": "account"
        }
      },
      "organisation": {
        "data": {
          "id": "1323",
          "type": "organisation"
        }
      }
    }
  }
}

The best way to accomplish this would maybe be to add a new relationship set on the base class.

private getter _relationships : Set(String)

Then, populate this based on the RELATIONS constant and includes args.

I believe this should have a very minor performance hit

Thoughts?

Copy link
Owner

@mjeffrey18 mjeffrey18 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a comment on speed, I can help out with some of these changes also. Ping me when you have had a chance to go over everything.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants