I'm trying to make two relationships:
ProductsxColors
ProductsxSizes
Products Table:
class Products extends Table{
IntColumn get idProduct => integer().autoIncrement()();
//more fields...
}
Colors Table:
class Colors extends Table {
IntColumn get idColor => integer().autoIncrement()();
TextColumn get name => text().withLength(min: 1, max: 100)();
TextColumn get value => text().withLength(min: 1, max: 100)();
}
Sizes Table:
class Sizes extends Table {
IntColumn get idSize => integer().autoIncrement()();
TextColumn get size => text().withLength(min: 1, max: 100)();
}
A Product can have many Sizes and Colors.
I've already read moor documentation, but only found examples for entities with one relationship.
Solved using queries.
First, add transactional tables:
class ProductsWithColors extends Table{
IntColumn get idProductWithColor => integer().autoIncrement()();
IntColumn get product => integer()();
IntColumn get color => integer()();
}
class ProductsWithSizes extends Table{
IntColumn get idProductWithSize => integer().autoIncrement()();
IntColumn get product => integer()();
IntColumn get size => integer()();
}
Then, create a Dao for query
#UseDao(tables: [Products, Colors, ProductsWithColors, ProductsWithSizes, Sizes],
queries: {
'productsWithSizesAndColors':
'SELECT * FROM products INNER JOIN products_with_colors ON
products_with_colors.product = products.id_product INNER JOIN
colors ON colors.id_color = products_with_colors.color
INNER JOIN products_with_sizes ON products_with_sizes.product = products.id_product
INNER JOIN sizes ON sizes.id_size = products_with_sizes.size
WHERE <condition> = :idc'
}
)
This generates the following methods:
Future<List<ProductsWithSizesAndColorsResult>> productsWithSizesAndColors(int idc) {
return productsWithSizesAndColorsQuery(idc).get();
}
Stream<List<ProductsWithSizesAndColorsResult>> watchProductosWithSizesAndColors(int idc) {
return productsWithSizesAndColorsQuery(idc).watch();
}
the insert method goes this way:
Future insertProductWithSizesAndColors async(List<Color> colors, List<Size> sizes, Product product){
colors.forEach((c) async {
await db.colorsDao.insertColor(ColorsCompanion(idColor: moor.Value(c.idColor), ..);
var pwc = ProductsWithColorsCompanion(color: moor.Value(c.idColor), product: moor.Value(product.idProduct));
await db.productsWithColorsDao.insertProductWithColor(pwc);
});
sizes.forEach((t) async {
await db.sizesDao.insertSize(SizesCompanion(idSize: moor.Value(t.idSize),..);
var pwt = ProductsWithSizesCompanion(size: moor.Value(t.idSize),product: moor.Value(product.idProduct));
await db.productsWithSizesDao.insertProductWithSize(pwt);
});
await db.productsDao.insertProduct(ProductsCompanion(idProduct: moor.Value(product.idProduct),..));
});
}
If a product can have many colors and sizes, and each color and size can have multiple colors then you need two tables. One to link colors with products, and other to link sizes con products.
This is the example you mention with only one of those relationships: https://moor.simonbinder.eu/docs/examples/relationships/
So now do it with two. (feel free to change the tables names for something more fitting)
Table to products with colors:
class ProductsColors extends Table{
IntColumn get idProductsColors => integer().autoIncrement()();
IntColumn get Product => integer()();
IntColumn get Color => integer()();
}
Table to products with sizes:
class ProductsSizes extends Table{
IntColumn get idProductsColors => integer().autoIncrement()();
IntColumn get Product => integer()();
IntColumn get Size => integer()();
}
To add a color or a size to a product, just create the entity with the correct ids and add it to the database.
To select the colors of an specific group do this with id "idProductX":
final SimpleSelectStatement<Colors, Color> colorQuery =
select(colors)
..join(<Join<Table, DataClass>>[
innerJoin(
productsColors,
productsColors.Color.equalsExp(colors.idColor) &
productsColors.Product.equals(idProductX),
),
]);
List<Colors> = colorQuery.get();
There should be an equivalent for Sizes. Tell me if it works for you.
For extra security you should make the ints referencing other tables foreign keys like this:
class ProductsColors extends Table{
IntColumn get idProductsColors => integer().autoIncrement()();
IntColumn get Product => integer()().customConstraint('REFERENCES products(idProduct)')();
IntColumn get Color => integer()().customConstraint('REFERENCES colors(idColor)')();
}
But you'll need to add this at the database service to activate the foreign keys:
#override
MigrationStrategy get migration =>
MigrationStrategy(
beforeOpen: (OpeningDetails details) async {
await customStatement('PRAGMA foreign_keys = ON');
},
);
Related
because I don't want use cascade to update the join table and I want custom columns, I've created a custom many to many relationship. However when I query the relation it only provides the values in the join table and doesn't pull the relation data.
User
#Entity('user')
export class User {
#PrimaryColumn()
id: string;
#OneToMany(
() => UserArtistFollowing,
(userArtistFollowing) => userArtistFollowing.user
)
following: UserArtistFollowing[];
}
Artist
#Entity('artist')
export class Artist {
#PrimaryGeneratedColumn('uuid')
id: string;
#OneToMany(
() => UserArtistFollowing,
(userArtistFollowing) => userArtistFollowing.artist
)
usersFollowing: UserArtistFollowing[];
}
UserArtistFollowing
#Entity('userArtistFollowing')
export class UserArtistFollowing {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column()
userId!: string;
#Column()
artistId!: string;
#ManyToOne(
() => User,
(user) => user.following
)
user!: User;
#ManyToOne(
() => Artist,
(artist) => artist.usersFollowing
)
artist!: Artist;
#CreateDateColumn()
createdAt!: Date;
#UpdateDateColumn()
updatedAt!: Date;
}
Query
const user = await getManager()
.getRepository(Models.User)
.findOne({
where: { id: id },
relations: ['following'],
});
So that query only provides the id, userId, artistId, but not the Artist array of objects (which is the data I need).
Thoughts?
After further testing, turns out you can use find -> relations with these kinds of custom many to many relationships. You just have to specify the relation in dotted notation for nested relations.
const user = await getManager()
.getRepository(Models.User)
.findOne({
where: { id },
relations: [
// you have to specify the join table first (following) to retrieve the columns in the join table
'following',
// then you use dotted notation to get the relation from the join table
'following.artist',
// another example of a deeply nested relation
'favourites',
'favourites.song',
'favourites.song.supportingArtists',
'favourites.song.supportingArtists.artist',
],
});
You can also use join with a nested leftJoinAndSelect, but its more tedious.
const user = await getManager()
.getRepository(Models.User)
.findOne({
where: { id },
join: {
alias: 'user',
leftJoinAndSelect: {
following: 'user.following',
artists: 'following.artist',
},
},
});
Here is the updated entities
UserArtistFollowing
#Entity('userArtistFollowing')
export class UserArtistFollowing {
#PrimaryColumn()
userId: string;
#PrimaryColumn()
artistId: string;
#ManyToOne(
() => User,
(user) => user.following
)
user!: User;
#ManyToOne(
() => Artist,
(artist) => artist.usersFollowing
)
artist!: Artist;
#CreateDateColumn()
createdAt!: Date;
#UpdateDateColumn()
updatedAt!: Date;
}
Artist
#Entity('artist')
export class Artist {
#PrimaryGeneratedColumn('uuid')
id: string;
#OneToMany(
() => UserArtistFollowing,
(userArtistFollowing) => userArtistFollowing.artist
)
usersFollowing: UserArtistFollowing[];
}
User
#Entity('user')
export class User {
#PrimaryColumn()
id: string;
#OneToMany(
() => UserArtistFollowing,
(userArtistFollowing) => userArtistFollowing.user
)
following: UserArtistFollowing[];
}
If you always want the artist model fully hydrated, you could try setting eager: true in your RelationOptions.
https://typeorm.io/#/eager-and-lazy-relations/eager-relations
If you don't want that, then your solution makes sense.
I try to create form with html select element using EntityType. I must get values by some condition and by this condition necessary default value not select from database. So i get all options values, without one, that must be a default value. So i try to find a way to put this value to select. What i tried...
Set value in form:
$growing = $em->getRepository('FarmBundle:Growing')->findGrowing($user_id);
$garden = $em->getRepository('FarmBundle:Garden')->find(7);
$tableForm = $this->createForm('FarmBundle\Form\GrowingType', $growing, ['user_id' => $user_id]);
$tableForm->get('garden')->setData($garden);
$form = $tableForm->createView()
Then i tried to set data in entity:
$growing = $em->getRepository('FarmBundle:Growing')->findGrowing($user_id);
$garden = $em->getRepository('FarmBundle:Garden')->find(7);
$growing->setGarden($garden);
$tableForm = $this->createForm('FarmBundle\Form\GrowingType', $growing, ['user_id' => $user_id]);
$form = $tableForm->createView()
Then i tried to set default select value in form_builder using 'data' attribute:
$growing = $em->getRepository('FarmBundle:Growing')->findGrowing($user_id);
$garden = $em->getRepository('FarmBundle:Garden')->find(7);
$tableForm = $this->createForm('FarmBundle\Form\GrowingType', $grow, [
'user_id' => $user_id,
'selected_choice' => $garden
]);
$form = $tableForm->createView();
Form_builder code:
class GrowingType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('id', HiddenType::class)
->add('garden', EntityType::class , [
'class' => 'FarmBundle\Entity\Garden',
'query_builder' => function (GardenRepository $gr) use ($options) {
return $gr->queryFreeGardens($options['user_id']);
},
'attr' => [
'data-type' => 'text',
'class' => 'table-select',
'disabled' => true
],
'required' => false,
'data' => $options['selected_choice']
]);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'FarmBundle\Entity\Growing',
'selected_choice' => null,
'user_id' => null
));
}
}
And code of query for query builder:
class GardenRepository extends \Doctrine\ORM\EntityRepository
{
public function queryFreeGardens($user_id)
{
$qb = $this->createQueryBuilder('g')
->leftJoin('g.growing', 'grow')
->where('grow.plantDate is NULL')
->orWhere('grow.endDate is not NULL')
->andWhere('g.user = :user_id')
->orderBy('g.name')
->setParameter('user_id', $user_id);
return $qb;
}
}
And all of this 3 methods not works. Result is one, if entity not get for query in query builder, i cant set this entity. If i will set entity as default value, that was in query builder all will works fine.
How can i solve this problem?
try this
in controller:
$growing = $em->getRepository('FarmBundle:Growing')->findGrowing($user_id);
$garden = $em->getRepository('FarmBundle:Garden')->find(7);
$tableForm = $this->createForm('FarmBundle\Form\GrowingType', $grow, [
'user_id' => $user_id,
'additional_id' => 7
]);
$form = $tableForm->createView();
in form:
class GrowingType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('id', HiddenType::class)
->add('garden', EntityType::class , [
'class' => 'FarmBundle\Entity\Garden',
'query_builder' => function (GardenRepository $gr) use ($options) {
return $gr->queryFreeGardens($options['user_id'], $options['additional_id');
},
'attr' => [
'data-type' => 'text',
'class' => 'table-select',
'disabled' => true
],
'required' => false
]);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'FarmBundle\Entity\Growing',
'additional_id' => null,
'user_id' => null
));
}
}
in repository:
class GardenRepository extends \Doctrine\ORM\EntityRepository
{
public function queryFreeGardens($user_id, $additional_id)
{
$qb = $this->createQueryBuilder('g')
->leftJoin('g.growing', 'grow')
->where('grow.plantDate is NULL')
->andWhere('g.id = :additional_id')
->orWhere('grow.endDate is not NULL')
->andWhere('g.user = :user_id')
->andWhere('g.id = :additional_id')
->orderBy('g.name')
->setParameter('user_id', $user_id)->setParameter('additional_id', $additional_id);
return $qb;
}
}
maybe you will need to adjust your repository method to retrieve values in right way. There are or clause, you should add this additional id to both branches of your or clause. The main idea is to retrieve you selected object too.
I am looking for the best method for creating/adding dynamic options in a form. By options, I mean things like choice value pairs, or maybe even default values. I can see at least three options:
1) add the options to the $options array when adding the form type. For this, it appears that I must first declare a default value and then add them in the add method and in the controller:
controller:
$choices = [];
foreach ($pages as $page) {
$choices[$page->getId()] = $page->getTitle();
}
$options = ['pages' => $choices];
$form = $this->createForm('MyBundle\Form\Type\PageType', $data, $options);
FormType:
class PageType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('pid', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', [
'choices' => $options['pages'],
'label' => __('Page')
]);
}
...
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'pages' => []
]);
}
}
2) If the values are not dependent on controller values, it seems I could create them in the OptionsResolver (assuming access to the source data)
FormType:
class PageType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('pid', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', [
'choices' => $options['pages'],
'label' => __('Page')
]);
}
...
public function configureOptions(OptionsResolver $resolver)
{
$choices = [];
$pages = $this->getPages();
foreach ($pages as $page) {
$choices[$page->getId()] = $page->getTitle();
}
$resolver->setDefaults([
'pages' => $choices
]);
}
3) Finally, I can also add in the buildForm method (again assuming access to source data):
FormType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$choices = [];
$pages = $this->getPages();
foreach ($pages as $page) {
$choices[$page->getId()] = $page->getTitle();
}
$builder
->add('pid', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', [
'choices' => $choices,
'label' => __('Page')
]);
}
Obviously, there is the most flexibility in the first option, but If I do not require that flexibility, or do not want to manage the options in the controller for some reason, does it make more sense to do the work in the buildForm or configureOptions methods?
If you require flexibility you can't use solution 3. But if you want to avoid flexibility, solution 3 is the best.
Solution 1 and 2 are OK, it really depend of what you need :
If you use your form in several actions with different choices: use solution 1, but add a requirement on this option to prevent the form to be called without choices
If your choices are often the same, but you want to override them only sometimes: chose solution 2
Personally I prefer the solution 1, because it's always better if your form relies on the less possible external objects ($this->pages in your example).
Regards
If you work with Doctrine Entities, you should use this:
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
// ...
$builder->add('pid', EntityType::class, array(
'class' => 'AppBundle:Page',
'choice_label' => 'title',
));
For working with another type of objects this one:
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use AppBundle\Entity\Page;
// ...
$builder->add('pid', ChoiceType::class, [
'choices' => [
new Page('Page 1'),
new Page('Page 2'),
new Page('Page 3'),
new Page('Page 4'),
],
'choices_as_values' => true,
'choice_label' => function($page, $key, $index) {
/** #var Page $page */
return $page->getTitle();
}
]);
More information you can read in blog post here.
Trying to make a form that a user chooses an option and depending on their choice loads additional fields. So far I have a UserSignupType:
class UserSignupType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('userType', ChoiceType::class, array(
'choices' => array(
"Subscriber" => "Subscriber",
"Friend" => "Friend"
),
'expanded' => true,
'mapped' => false
));
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) {
$form = $event->getForm();
$usertype = $form->get('userType')->getData(); //updated per JBaffords answer
if($userType == "Subscriber")
{
$builder->add('agency', EntityType::class, array(
"class" => "\AppBundle\Entity\Agency",
"label" => "name"));
}
elseif($userType == "Friend")
{
$builder->add('phoneNumber', PhoneNumberType::class, array(
'default_region' => 'US',
'format' => PhoneNumberFormat::NATIONAL));
}
}
);
}
// ...
}
not sure if the getData method is the right method to use, and if it is, i need to somehow get the "userType" field out of it. I cant call getUserType because its not an actual mapped property and I don't want it to be. It simply decides the fields to show.
You can get the value for any form element (mapped or unmapped) by doing:
$form->get('fieldName')->getData();
get() returns a Form object, so if you have a nested form, you can continue to call ->get('nextFieldName') on each child until you get to the form element you need.
The value returned from getData for a form is going to depend on (amont other things) the mapping of its child elements. If the form has no children, then its value is its value; the mapping just determines whether that value is populated into its parent's data.
In your specific case, to get the data for the userType element, you would do:
$userType = $form->get('userType')->getData();
I want to fetch total records for my product group by product title and order by product title. I am using monogomapper in ror.
I tried :
tetsing code
#test_product_details_array_full=Product.collection.aggregate([
{"$match" => {:store_id => #store_id, :product_active=> "yes"}},
{"$project"=> {"product_name"=> 1, "output"=> { "$toLower"=> "$product_name" }}},
{"$sort"=> { "output"=>1 } },
{"$project"=> {product_name: 1, _id:0}},
{"$group" => {_id: "$product_name", Product: { "$push"=> "$$ROOT"}}},
]);
testing code
I am getting blank array in out put. So how do I get proper result.