8000 How to stop generation of redundant parts of object graph · Issue #1183 · naver/fixture-monkey · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

How to stop generation of redundant parts of object graph #1183

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
adambridger opened this issue May 22, 2025 · 5 comments
Open

How to stop generation of redundant parts of object graph #1183

adambridger opened this issue May 22, 2025 · 5 comments
Labels
bug Something isn't working question Further information is requested
Milestone

Comments

@adambridger
Copy link
adambridger commented May 22, 2025

Version 1.1.11

I have a massive model with many circular dependencies (bi-direction mappings). Im using setNull to break the circular dependencies, and im using map(...) to persist some dependent children into the database as the sample is generated.

However even though the circular dependency has been broken, an object for that property (which eventually results in null) is still created along withs its children. Due to how im using map, some of these redundant objects are getting persisted.

After much exploring of the API, I couldnt find a customization that prevents generation of redundant parts of the object graph.

Below is a small example to illustrate the problem described above.

Let me know if there is customization to stop creation of redundant parts of the graph or if i should use a different strategy all together.

Many thanks.

@Data
@Entity
@NoArgsConstructor
public static class Foo {
    private Bar bar;
    private String f;

    public void setBar(Bar bar) {
        this.bar = bar;
        this.bar.setFoo(this);
    }
}

@Entity
@Data
@NoArgsConstructor
public static class Bar {
    @ToString.Exclude
    private Foo foo;
    private String b;
}

    FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
            .register(Foo.class, fm -> fm.giveMeBuilder(Foo.class)
                            .map(o -> persist(o))
            )
            .register(Bar.class, fm -> fm.giveMeBuilder(Bar.class)
                            .map(o -> persist(o))
            )

    Foo foo = fixtureMonkey.giveMeBuilder(Foo.class)
            .map(o -> persist(o))
            .sample();

//returns a Foo with a Bar with a different Foo and null Bar

    Foo sample = fixtureMonkey.giveMeBuilder(Foo.class)
            .setNull("bar.foo") //cyclic reference
            .sample();

//returns a Foo with a Bar with the parent Foo (populated by bar.setFoo). However a distinct Bar and a distinct Foo is also constructed, the foo is persisted, but then they are discarded.

@adambridger adambridger added the question Further information is requested label May 22, 2025
@adambridger
Copy link
Author

Ive just noticed that:
.set("bar.foo", Values.just(null))
works different to:
.setNull("bar.foo")

The former does seem to stop generation of redundant parts of object graph. However i need to do more testing to confirm

@adambridger
Copy link
Author
adambridger commented May 23, 2025

Did some more testing and found scenario causing stackoverflow with fixtureMonkeyBuilder.register:

@Data
@NoArgsConstructor
public static class Parent {
    @ToString.Exclude
    private List<Child1> children;
    private String str;
}


@Data
@NoArgsConstructor
@AllArgsConstructor
public static class Child1 {
    @ToString.Exclude
    private Parent parent;
    @ToString.Exclude
    private Child2 child;
    private String str;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class Child2 {
    @ToString.Exclude
    private Child1 parent;
    @ToString.Exclude
    private Child3 child;
    private String str;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class Child3 {
    @ToString.Exclude
    private Child2 parent;
    @ToString.Exclude
    private Child4 child;
    private String str;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class Child4 {
    @ToString.Exclude
    private Child3 parent;
    private String str;
}

Scenario 1: "set parent.children to just null with ArbitraryBuilder works as expected"

    FixtureMonkey monkey = FixtureMonkey.builder()
            .defaultNotNull(true)
            .nullableContainer(false)
            .defaultArbitraryContainerInfoGenerator(context -> new ArbitraryContainerInfo(1, 3)
            .register(Parent.class, fm -> fm.giveMeJavaBuilder(Parent.class)
            )
            .build();

    Parent sample = monkey.giveMeBuilder(Parent.class)
            **.set("children", Values.just(null))**
            .sample();

no problems, no redundant child classes are instantiated

Scenario 2: "set parent.children to just null with FixtureMonkeyBuilder.register results in stackoverflow"

    FixtureMonkey monkey = FixtureMonkey.builder()
            .defaultNotNull(true)
            .nullableContainer(false)
            .defaultArbitraryContainerInfoGenerator(context -> new ArbitraryContainerInfo(1, 3))
            .register(Parent.class, fm -> fm.giveMeJavaBuilder(Parent.class)
                    **.set("children", Values.just(null))**
            )
            .build();

    Parent sample = monkey.giveMeBuilder(Parent.class)
            .sample();

stackoverflow

Scenario 3: "set parent.children to null with FixtureMonkeyBuilder works but not as expected"

    FixtureMonkey monkey = FixtureMonkey.builder()
            .defaultNotNull(true)
            .nullableContainer(false)
            .defaultArbitraryContainerInfoGenerator(context -> new ArbitraryContainerInfo(1, 3))
            .register(Parent.class, fm -> fm.giveMeJavaBuilder(Parent.class)
                    .setNull("children")
            )
            .build();

    Parent sample = monkey.giveMeBuilder(Parent.class)
            .sample();

**no stackoverflow, but redundant child classes are instantiated **

ref: https://naver.github.io/fixture-monkey/v1-1-0/docs/fixture-monkey-options/overview/#3-register-option---setting-type-specific-default-rules

@seongahjo
Copy link
Contributor
seongahjo commented May 28, 2025

@adambridger
Hello, sorry for the late response.

Ive just noticed that: .set("bar.foo", Values.just(null)) works different to: .setNull("bar.foo")

Yes, set(.., Value.just(null)) and setNull work differently.

Value.just(null) does not generate the child properties of given type.

I have looked at your cases, but I don't fully understand the problem.
I have a few questions.

  1. What is the method of persist in Foo or Bar?
  2. I cannot reproduce the Stack Overflow scenario in scenario 2. Am I missing something?
Image

Thank you.

@adambridger
Copy link
Author

Hi mate. Thanks for your time on a very helpful utlity.

Here is how I reproduced stackoverflow.

    @Data
    @NoArgsConstructor
    public static class Parent {
        @ToString.Exclude
        private List<Child1> children;
        private String str;
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Child1 {
        @ToString.Exclude
        private Parent parent;
        private String str;
    }

        FixtureMonkey monkey1 = FixtureMonkey.builder()
                .defaultNotNull(true)
                .nullableContainer(false)
                .defaultArbitraryContainerInfoGenerator(context -> new ArbitraryContainerInfo(1, 3))
                .register(Parent.class, fm -> fm.giveMeBuilder(Parent.class)
                        .map(o -> o)
                )
                .register(Child1.class, fm -> fm.giveMeBuilder(Child1.class)
                        .set("parent", Values.just(null))
                )
                .build();

Its related the map() in register.

My use case is that I want to randomly generate an ORM model that is very deep. Im using Value.just(null) to cutoff circular references and parts of model that I dont want to generate. Some generated objects i want to persist in a database during generation. I solved this with a custom ArbitraryGenerator, however i did consider using the map but I find that map works strange. I seems like a simple way to modify an already generated object, however after map runs, the object is regenerated again.

@seongahjo seongahjo added the bug Something isn't working label May 28, 2025
@seongahjo seongahjo added this to the 1.1.12 milestone May 28, 2025
@seongahjo
Copy link
Contributor
seongahjo commented May 28, 2025

@adambridger
It's actually a bug. It occurs when you use map, zip, thenApply or acceptIf in the register option.

In your case, I recommend using the pushPropertyGenerator to exclude properties with circular dependencies.
I will address this issue in 1.1.12. However, it is much more efficient to use the propertyGenerator.

I seems like a simple way to modify an already generated object, however after map runs, the object is regenerated again.

All ArbitraryBuilders are generated lazily. That's why it regenerated again. In your case, you also need to use 'fixed'.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working question Further information is requested
Projects
None yet
2FAB
Development

No branches or pull requests

2 participants
0