I work on an application where users can design their own custom forms. I'm using Sequelize and Postgress.
It has an online form where users can create their custom forms. I have two entities where it should show forms for Event and Event Type.
The Event can be of different Event Type.
Each Event Type has its form created through form builder.
Event should show different form per chosen Event Type.
Saving the Event should save all fields including those of that event Type, and those values should be associated to event now.
I have used the following post to model Form Builder:
Event
-------
id
name
typeId (FK to eventType.id)
Event Type
-----
id (PK)
name
Form_elements
-------------
id (PK)
eventType_id (FK to eventType.id)
element_type_id (FK to element_types.id)
Element_types
-------------
id (PK)
name
Element_list_values
-------------------
id (PK)
element_id (FK to form_elements.id)
event_id (FK to event.id)
value
When user is on Add or Edit Form how would a select query to generate table from the form fields and the field_values from this schema?
I have tried the following code:
formField.findAll({
where: {
eventTypeId: typeId,
},
include: [{
model: formFieldType,
},
{
model: formFieldValue,
as: 'FormFieldValues',
}],
});
But I keep getting :
SequelizeEagerLoadingError: formFieldValue is not associated to formField!
Here are my model associations:
EventType.hasMany(FormField);
FormField.belongsTo(EventType);
FieldType.hasMany(FormField, { foreignKey: 'typeId', as: 'type' });
FormField.belongsTo(FieldType, { foreignKey: 'typeId' });
// field value
Event.belongsToMany(FormField, { through: 'formFieldValues', foreignKey: 'event_id' });
FormField.belongsToMany(Event, { through: 'formFieldValues', foreignKey: 'field_id' });
Related
I have doctor and specialization table, and have doctor_specialization_pivot table. In my pivot table I have the following columns:
| doctor_id | additional_data | specialization_id |
additional_data is from the doctor model along with the doctor_id.
In my doctor model file, I have this relationship:
public $belongsToMany = [
'specialization' => [
'path\to\specialization\model',
'table' => 'doctor_specialization_pivot',
'parentKey' => 'doctor_id',
'otherKey' => 'specialization_id',
]
];
Now during submit of form, I'm getting this error:
SQLSTATE[HY000]: General error: 1364 Field 'additional_data' doesn't have a default value (SQL: insert into doctor_specialization_pivot (doctor_id, specializations_id) values (1, 3))"
I tried adding to my relationship 'pivot' => ['additional_data']. But still getting the same error.
I checked the submitted data and additional_data is not empty. I checked from OctoberCMS forums but not getting straight forward answers such as this and this.
Okay. I found the answer to my own question.
I'll answer in detail to help everyone. After digging and blind shooting. According to this documentation here, we can use the method attach() to attach a role to a user by inserting a record in the intermediate table that joins the models.
What confuse me in the documentation is that it uses a $roleId variable and I didn't understand where the $roleId came from. If it's the id of the parent table or the id of other table.
Sample from the link:
$user = User::find(1);
$user->roles()->attach($roleId);
So what I did in my doctor model, I hook to the event beforeSave, use the relationship ($this->specialization) as the first parameter instead of the id in the docs. The $this->specialization() is the relationship too defined in belongsToMany.
Answer:
public function beforeSave()
{
$this->specialization()->attach($this->specialization,['additional_data' => 'additional data from doctor table']);
}
The implementation is pretty much like this video from Watch Learn (Ivan). You can learn a lot about OctoberCMS just by watching his guide on it. Here is the documentation on it as well. This is the example info that I have done.
WARNING Another known flaw is you can't apply this to a model record that isn't created yet. Unlike the standard relation widget which waits until it is saved before attaching records this attaches records in a separate overlay form.
Here is my model.php relationship:
public $belongsToMany = [
'equipments' => [
'Brandon\Pixelrpg\Models\Equipments',
'table' => 'brandon_pixelrpg_equipment_inventory',
'key' => 'inventory',
'otherKey' => 'equipment',
'pivot' => ['quantity']
]
];
Here is my controller.php:
public $implement = [
'Backend\Behaviors\ListController',
'Backend\Behaviors\FormController',
'Backend\Behaviors\ReorderController',
'Backend\Behaviors\RelationController'
];
public $listConfig = 'config_list.yaml';
public $formConfig = 'config_form.yaml';
public $reorderConfig = 'config_reorder.yaml';
public $relationConfig = 'config_relation.yaml';
Here is my config_relation.yaml:
equipments:
label: Equipments
view:
list:
columns:
id:
label: ID
type: number
searchable: true
sortable: true
name:
label: Name
type: text
searchable: true
sortable: true
value:
label: Value
type: number
searchable: true
sortable: true
updated_at:
label: Updated
type: datetime
searchable: true
sortable: true
pivot[quantity]:
label: Quantity
type: number
pivot:
form:
fields:
pivot[quantity]:
label: Quantity
type: number
default: 0
I am just going to make a new answer and assume is what you need because you have yet to show any code on how your form works. This is how I would update the pivot information from a frontend form.
Relationship in model.php:
public $belongsToMany = [
'specialization' => [
'path\to\specialization\model',
'table' => 'doctor_specialization_pivot',
'parentKey' => 'doctor_id',
'otherKey' => 'specialization_id',
'pivot' => ['additional_data'] //This is required
]
];
Then in some php code lets call it onAddSpecialization():
public function onAddSpecialization() {
//Calling a function to get the doctor id maybe from the signed in user
$doctor = Doctors::find($this->$doctorId());
//We get our Specialization from an input
$specialization = Specialization::find(Input::get('specialization_id'));
//We get our additional data from an input
$additional_data = Input::get('additional_data');
//Now we are going to attach the information
$doctor->specialization()->attach($specialization, ['additional_data' => $additional_data]);
}
Now an example of updating our additional data:
public function onUpdateAdditionalData() {
//Calling a function to get the doctor id maybe from the signed in user
$doctor = Doctors::find($this->$doctorId());
//If you get specialization by id from an input. I believe you need to go through the relationship in order to access the correct pivot information.
$specialization = $doctor->specialization->where('id', Input::get('specialization_id'))->first();
//Insert the new pivot information
$specialization->pivot->additional_data = $new_additional_data;
//Save
$specialization->pivot->save();
}
I'm using Sequelize in my project. These are the two models:
const User = db.define('user', {
name: Sequelize.STRING,
password: Sequelize.STRING
})
const Product = db.define('product', {
name: Sequelize.STRING,
price: Sequelize.INTEGER
})
Now users can purchase products and I have associations setup like below:
Product.belongsToMany(User, {through: 'UserProducts'})
User.belongsToMany(Product, {through: 'UserProducts'})
I also have this UserProducts table with an additional column.
const UserProducts = db.define('UserProducts', {
status: Sequelize.STRING
})
Sequelize creates a composite key with combination of userId and productId and will only allow one record with a combination of userId and productId. So, for example, userId 2 and productId 14.
This is a problem for me because sometimes people want to purchase multiple times. I need one of the following scenarios to work:
Don't use the composite key and instead have a completely new auto-increment column used as key in UserProducts.
Instead of making key with userId and productId alone, allow me to add one more column into the key such as the status so that unique key is a combination of the three.
I do want to use the associations as they provide many powerful methods, but want to alter the unique key to work in such a way that I can add multiple rows with the same combination of user id and product id.
And since my models/database is already running, I will need to make use of migrations to make this change.
Any help around this is highly appreciated.
If anyone else is having problems in v5 of Sequelize, it is not enough to specify a primary key on the 'through' model.
You have to explicitly set the unique property on the through model.
User.belongsToMany(Product, { through: { model: UserProducts, unique: false } });
Product.belongsToMany(User, { through: { model: UserProducts, unique: false } });
Belongs-To-Many creates a unique key when the primary key is not present on through model.
Since you also have additional property in your UserProducts table, you need to define the model for UserProducts and then tell the sequelize to use that model instead of creating its own
class User extends Model {}
User.init({
name: Sequelize.STRING,
password: Sequelize.STRING
}, { sequelize })
class Product extends Model {}
ProjProductect.init({
name: Sequelize.STRING,
price: Sequelize.INTEGER
}, { sequelize })
class UserProducts extends Model {}
UserProducts.init({
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
status: DataTypes.STRING
}, { sequelize })
User.belongsToMany(Project, { through: UserProducts })
Product.belongsToMany(User, { through: UserProducts })
refer: Sequelize v4 belongsToMany
UPDATE
since you are using v3 and already have a model created for your UserProducts table use following snippet
UserProducts = db.define('UserProducts', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
status: DataTypes.STRING
})
Since setting explicitly the unique property on the through model is not working in v6, the only solution i found is to define the 3 parts of the association this way :
User.hasMany(UserProducts);
UserProducts.belongsTo(User);
Product.hasMany(UserProducts);
UserProducts.belongsTo(Product);
You can then create your models and associations :
const user = await User.create(user_data);
const product = await Product.create(product_data);
const up = await UserProduct.create(up_data);
await up.setUser(user);
await up.setProduct(product);
If anyone has a better solution, I would be happy to know it
This is my College model
module.exports = {
attributes: {
name:{
type:'string',
required:true
},
location:{
type:'string',
required:true
},
faculties:{
collection:'faculty',
via:'college'
}
}
};
This is my Faculty model
module.exports = {
attributes: {
name:{
type:'string',
required:true
},
description:{
type:'string'
},
college:{
model:'college',
required:true
}
,
years:{
collection:'year',
via:'faculty'
}
}
};
My problem is that I can add new Faculty with any value in college attribute. If I don't have college with 3000 id, I can still add it but college attribute won't show up when I list all faculties. How can I prevent it from adding a faculty with invalid college id?
Currently waterline does not create foreign key constraints in the manner you describe. It only creates the associated field.
You can use a different library instead of Waterline such as Sequelize.js here is a link about how to go about doing that
https://groups.google.com/forum/#!topic/sailsjs/ALMxbKfnCIo
If your are using a SQL database you can manally create the foreign key constraint yourself.
Or you can validate the value of college before being set in your faculty model by checking in beforeValidate() or afterValidate() on your faculty model.
This question has been asked twice before in on SO:
https://stackoverflow.com/questions/10244753/extjs-many-to-many-association-how
Extjs 4.1 many-to-many model association
BUT neither of these questions have an actual answer, so I'm going to try again!
Let's say I have two models, User and Group. A user can be in many groups, and groups can contain many users. Here's the model code for User:
Ext.define('User', {
extend: 'Ext.data.Model',
alias: 'model.User',
fields: [
{name: 'username', type: 'string'},
...
],
proxy: {
// Omitted for brevity...
},
});
And Group:
Ext.define('Group', {
extend: 'Ext.data.Model',
alias: 'model.Group',
fields: [
{name: 'name', type: 'string'},
...
],
proxy: {
// Omitted for brevity...
},
});
Now, let's say I wanted a Grid which lists my groups, and allows me to double-click a group and edit which users are in that group in second grid.
Let's also say there's a lot of users per group, so I don't want to load all the associated users when I load the groups.
I want to be able get a store of users for a particular group, and give that to my grid, which will load data as needed (using the usual pagination that a grid does).
I see two potential approaches here. There may another better way, but I will outline what I've tried do so far below.
Intermediate model
Add another joining model
Add hasMany associations from User and Group to that model
Add belongsTo associations from my joining model back the way to User and Group.
Joining model code:
Ext.define('GroupUsers', {
extend: 'Ext.data.Model',
alias: 'model.GroupUsers',
fields: [
{name: 'group_id', type: 'int'},
{name: 'user_id', type: 'int'},
],
associations: [
{type: 'belongsTo', model: 'User'},
{type: 'belongsTo', model: 'Group'}
],
...
});
Association in Group:
associations: [
{type: 'hasMany', model: 'GroupUsers', name: 'group_users'}
],
I will now be able to access a store of GroupUsers for a particular Group:
group.group_users()
The problem with this approach, is that I can't just bind a store of GroupUsers to my second grid, because I want to display things like the user's name. I could iterate the store's items, fetch each User object with getUser(), add them to another store, and use that for my Grid, but that results in a server request per item! Alternatively, I could use my store of GroupUsers directly, but then would need to do something with renderers and I still need to fetch each User individually.
Direct association
Associate User and Group directly with a hasMany association on each
Associations on Group:
associations: [
{type: 'hasMany', model: 'User', name: 'users', foreignKey: '???'}
],
I can now get a store of actual User objects for a given group:
group.users()
Which would be great, except there's nothing for me to set the foreignKey of the association to. User can't have a group_id field, because a User can have many Groups!
Maybe this is not the answer you look for, but this is how I would solve this issue :
I would not link the groups and the users with extjs store associations, but rather on the server side.
In the controller of your grid put something like this :
init: function(){
this.control({
'grid': {itemdblclick: this.onGridItemdblclick}
})
},
onGridItemdblclick: function(grid, record){
var group_id = record.getId(),
usersStore = this.getStore('Users');
usersStore.load({params: {group_id: group_id}});
var win = Ext.widget('UsersGrid'); // adapt the code to your naming scheme
win.show();
}
The request to load the Users store will be sent with an extra parameter group_id. On the server side, your can use this extra parameter to filter your users.
I have a grid that pops up an edit form with combobox. Before I show the view I load the combobox store. Then I set the values of the form using form.loadRecord(record);. This loads the primary record ok. But how do I load the associated data value that is tied to the combobox so the combobox's value is set correctly.
I know I can use setValue manually, but I guess I was thinking could be handled via a load form.
If my form has 3 fields lets say firstName lastName and then a combobox of ContactType. The firstName and lastName are in my primary record with ContactType being the associated record. If I change values in fields lastName or firstName a change is detected and the record is marked as dirty. If I change the combobox value the record is not marked as dirty. I am guessing because they are two different models. How to make it one record. I would think having a combobox on a form that has its values set from a record in a grid is common but I can't find any examples of best way to accomplish this.
Here is some code.
My json looks like this. Primary record has firstName lastName hasOne associated record is ContactType
{
"__ENTITIES":[
{
"__KEY":"289",
"__STAMP":12,
"ID":289,
"firstName":"a",
"middleName":"",
"lastName":"asd",
"ContactType":{
"__KEY":"2",
"__STAMP":4,
"ID":2,
"name":"Home"
}
}
]
}
Controller list function,edit function and updatefunction, fires when grid row is clicked
list: function () {
var mystore = this.getStore('Contacts')
mystore.proxy.extraParams = { $expand: 'ContactType'};
mystore.load({
params: {
},
callback: function(r,options,success) {
// debugger;
} //callback
}); //store.load
editContact: function (grid, record) {
//load store for combobox
var store = this.getStore('ContactTypes');
store.load({
params: {
},
callback: function(r,options,success) {
// debugger;
} //callback
}); //store.load
var view = Ext.widget('contactsedit');
var form = view.down('form')
//load primary record
form.loadRecord(record);
//load associated record
form.loadRecord(record.getContactType());
updateContact: function (button) {
var win = button.up('window'),
form = win.down('form'),
record = form.getRecord(),
values = form.getValues(),
store = this.getStore('Contacts')
if (form.getForm().isValid()) {
if (this.addnew == true) {
store.add(values);
} else {
record.set(values);
}
store.sync();
win.close();
}
}
The loadRecord(record.getContactType) is a getter to the associated data. I am able to get the associated data but not sure how to make it set the value in the combobox or how to get it to act as one record and detect changes automatically.
My Contacts Model
Ext.define('SimplyFundraising.model.Contact', {
extend : 'Wakanda.model',
requires:[
'SimplyFundraising.model.ContactType'
],
fields: ['firstName', 'middleName','lastName','ContactType.name'],
associations: [
{
type: 'hasOne',
model: 'SimplyFundraising.model.ContactType',
name: 'ContactTypes',
getterName: 'getContactType',
associationKey: 'ContactType'
}
]
});
ContactType model
Ext.define('SimplyFundraising.model.ContactType', {
extend : 'Wakanda.model',
fields: ['__KEY','name',]
});
Is this the proper way to set a value for a combobox in a form with nested data?
Should I not use associations and just put all fields in my Contact Model ie add all ContactType fields to Contact model, then data should be in the same record and change tracking would work. But this seems counter to the MVC pattern and counter to the reason for associations.
How do you guys handle this scenario, any examples would be great!
Thanks,
Dan
if your form is loading the values correctly, then all you need to do is this:
Extjs4, How to set displayField in combo editor?