-
Notifications
You must be signed in to change notification settings - Fork 232
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
Current object as Randomizer#getRandomValue's parameter #6
Comments
This point has been already raised and we are trying to find the best way to make randomizers collaborate together to add some "quality" to the generated data. Another typical usecase is the country and city randomizers: If the country randomizer generates "France" for the country, the city randomizer should only generates french cities. I think we should define a clear contract of what the context object should contain (beside the currently populated object). |
Hey! There could be another interface that extends current Randomizer interface because context object will not be needed for every Randomizer. And it could have a setContext method. Context object that will be passing to setContext method should contain currently populated object and needed fields for randomization. Example:
firstname and lastname fields will be passed for this situation.
And country field will be passed for this. I wonder what kind of implemantation do you have in your mind and what do you think about this? |
Hi @muhammedf Thank you for this suggestion! This is what I had in mind, some
This is the most important decision in the story, what to put in the context object. Another interesting use case is issue 188, where we need to have access to the object graph to be able to implement the 'dotted' notation. The goal is to define a clear contract of what the population context contains. @eric-taix This feature request is very interesting that's why I didn't want to close it even though it took time.. I'm really sorry for this. If only I could work full time on OSS projects 😡 Kind regards |
@benas Don't worry, working on OSS projects as part-time is always difficult and unfortunately I have not enough time to help you right now. I like your ContextAwareRandomizer suggestion BTW you already did a very good job with Random-bean project |
@benas I examined the issue #188. As I understand it is about different treatments for objects with same type and same hierarchy but different depths. I think in that situation, object graph is really needed. Example scenario for email randomization:
Randomizing a Department instance: (for showing that dotted notations are relative to the current object)
And again of course dependent fields must be randomized before email. (I could not find a term for this :) |
Hi, I (finally) managed to work on this feature and added a couple interfaces:
Random Beans will inject the randomization context in any context aware randomizer before calling its @lombok.Data
public class Person {
private String firstName;
private String lastName;
} Let's say we want to generate a last name depending on the first name (this is basically the use case in the first comment of this issue). A context aware import io.github.benas.randombeans.api.ContextAwareRandomizer;
import io.github.benas.randombeans.api.RandomizerContext;
/**
* A last name randomizer that depends on the first name of the currently randomized object.
* The currently randomized object can be retreived from the randomization context.
*/
public class LastNameRandomizer implements ContextAwareRandomizer<String> {
private RandomizerContext context;
@Override
public void setRandomizerContext(RandomizerContext context) {
this.context = context;
}
@Override
public String getRandomValue() {
if (context.getRandomizedType().equals(Person.class)) {
Person randomizedObject = (Person) context.getRandomizedObject();
String firstName = randomizedObject.getFirstName();
if (firstName != null && firstName.equalsIgnoreCase("james")) {
return "bond";
}
if (firstName != null && firstName.equalsIgnoreCase("daniel")) {
return "craig";
}
}
return null;
}
} and how to use it: import io.github.benas.randombeans.EnhancedRandomBuilder;
import io.github.benas.randombeans.api.EnhancedRandom;
import org.junit.jupiter.api.Test;
import static io.github.benas.randombeans.FieldDefinitionBuilder.field;
import static org.assertj.core.api.Assertions.assertThat;
public class ContextAwareRandomizationTests {
@Test
void testContextAwareRandomization() {
// given
String[] names = {"james", "daniel"};
EnhancedRandom enhancedRandom = new EnhancedRandomBuilder()
.randomize(field().named("firstName").ofType(String.class).inClass(Person.class).get(), new FirstNameRandomizer(names))
.randomize(field().named("lastName").ofType(String.class).inClass(Person.class).get(), new LastNameRandomizer())
.build();
// when
Person person = enhancedRandom.nextObject(Person.class, "nickname");
// then
String firstName = person.getFirstName();
String lastName = person.getLastName();
assertThat(firstName).isIn(names);
assertThat(lastName).isNotNull();
if (firstName.equalsIgnoreCase("james")) {
assertThat(lastName.equalsIgnoreCase("bond"));
}
if (firstName.equalsIgnoreCase("daniel")) {
assertThat(lastName.equalsIgnoreCase("craig"));
}
}
}
I deployed version That's only a first effort towards implementing this new feature, so any idea/suggestion of improvement is welcome! @PascalSchumacher All changes are in @eric-taix @muhammedf It would be great if you give it a try (see how to import a snapshot version here) and let me know if it works for you. Looking forward for your feedback! Kr, |
Looks good! I took a short look at changing the interface to public interface ContextAwareRandomizer<ContextType, RandomizedType> extends Randomizer<RandomizedType> {
void setRandomizerContext(RandomizerContext<ContextType> context);
} which would allow a bit shorter implementations like: public class LastNameRandomizer implements ContextAwareRandomizer<Person, String> {
private RandomizerContext<Person> context;
@Override
public void setRandomizerContext(RandomizerContext<Person> context) {
this.context = context;
}
@Override
public String getRandomValue() {
Person randomizedObject = context.getRandomizedObject();
String firstName = randomizedObject.getFirstName();
if (firstName != null && firstName.equalsIgnoreCase("james")) {
return "bond";
}
if (firstName != null && firstName.equalsIgnoreCase("daniel")) {
return "craig";
}
}
} but this is probably over-engineering for dubious gain. |
@PascalSchumacher Thank you for the feedback! Indeed, this will eliminate the test |
@PascalSchumacher I tested the approach you suggested in branch @Data
public class Person {
private String firstName;
private String lastName;
private String nickname;
private Foo foo;
}
@Data
public class Foo {
private String firstName;
private String lastName;
}
// then use the following field definitions
String[] names = {"james", "daniel"};
EnhancedRandom enhancedRandom = new EnhancedRandomBuilder()
.randomize(field().named("firstName").ofType(String.class).get(), new FirstNameRandomizer(names))
.randomize(field().named("lastName").ofType(String.class).get(), new LastNameRandomizer())
.build(); So I think we can stick to the current API for now (which I polished after further testing). |
Yes, the current API is fine. Let's stick to it. 👍 |
After further testing of these new APIs, I noticed that the randomization context should contain a reference to the root object being randomized in addition to the currently randomized object in the object graph. Here is an example: When RB tries to create a random person, it will iterate through its fields. At some point, it will find that /**
* A city randomizer that depends on the country of the currently randomized object.
* The currently randomized object can be retrieved from the randomization context.
*/
public class CityRandomizer implements ContextAwareRandomizer<City> {
private RandomizerContext context;
@Override
public void setRandomizerContext(RandomizerContext context) {
this.context = context;
}
@Override
public City getRandomValue() {
if (context.getCurrentObject() instanceof Person) {
Person person = (Person) context.getCurrentObject();
Country country = person.getCountry();
if (country == null) {
return null;
}
String countryName = country.getName();
if (countryName != null && countryName.equalsIgnoreCase("france")) {
return new City("paris");
}
}
return null;
}
} This won't work as This new change is pushed in the latest v3.9.0-SNAPSHOT. Looking forward to getting feedback on this feature request soon. |
@eric-taix Have you got a chance to take a look at this new feature? The new APIs should cover the use case you mentioned. Here is ready to use maven project to test the feature: Looking forward for your feedback. |
Issue #188 was another good use case for this feature as well: the question is how to know the current field being randomized in the object graph (See example here). This additional use case allowed me to polish the API with a couple of new methods to get the current field being randomized (with dotted notation) as well as the current depth level in the hierarchy. I think the current API is quite ok after all these tests. But let's iterate on this. I merged the current status to the master branch for v3.9 and we can improve things in later versions. @eric-taix I'm closing this issue for now but don't hesitate to comment here if you want. As a side note: This is definitely one of the best issues I've worked on in the history of the project. |
It could be useful if Randomizer can access the current object.
A typical usecase is an email Randomizer for a Person:
If Randomizer can have access to the current objet then we can provide another EmailRandomizer which instead of generating the email address from a list, takes into account the current firstname and lastname and can (for example) retrieve the domain name from a list.
firstname: eric
lastname: taix
=> email: eric.taix@
The text was updated successfully, but these errors were encountered: