Saving a user in CakePHP with ACL group name specified rather than group id - cakephp

I have set up a Cake 2.3.1 app with ACL following this tutorial so I have a users table and a groups table.
Everything is working fine, but I'm a little confused about how I should go about saving new users since I don't want the user to be able to select their own group. The group will be chosen based on a number of things that the user inputs on the form so it will be determined by conditional statements in the controller.
At the moment I'm doing this:
$this->request->data['User']['group_id'] = '2'; (or 1, 3 or 4 depending on conditional statements in the controller).
Then, I'm doing a save on $this->data. This works but I'd like to be able to specify the group name rather than hardcode the id so that when we move to production and clear the database, it won't matter if the groups are added in a different order.

I see two ways of doing this.
The first I haven't tried, but it would be to change the primary key of the group table to the name of the group and set the primaryKey variable in the Group model and all subsequent reference and associations with this table in all the other models. That would probably bring you more than a headache and I wouldn't recommend it because you'd probably break the ACL component one way or another.
The other way is what I'd do, and it requires a bit of programming on your part (Cake doesn't have an easy way of doing this that I know of).
In the User model, put a beforeSave and in that function, create a dummy variable with the group name, do the lookup of the name and insert the correct id, and delete that variable.
On User.ctp model
public function beforeSave() {
if (isset($this->data[$this->alias]['group_name'])) {
//do the lookup with the group_name
$group = $this->Group->find('first', array('conditions'=>array('name'=>$this->data[$this->alias]['group_name'])));
if (count($group) <= 0)
//... throw an Exception or handle any way you want this case
$real_group_id = $group['Group']['id'];
$this->data[$this->alias]['group_id'] = $real_group_id;
unset($this->data[$this->alias]['group_name']);
}
return true; //remember this is important! otherwise the save will stop
}
When you want to change the user's group or add another user, just do this in the controller
$data_to_save['User']['group_name'] = 'webuser';
and the beforeSave function will take care of the rest.
I realize you asked for a Cake-easy-way of doing this, but I guess there's none (not sure though, feel free to correct me), the beforeSave function could save you a lot of repetitive code on the controllers. The only thing you'd have to be careful when clearing the database or changing group ids is to reassociate them correctly with the users already in the database, but since is a "moving to production" change, those users already predefined should be few or none.

Related

Dealing with 3NF in terms of database domain modelling where attributes are added knowing they create transitive dependency

I am currently working on setting up a database domain model, where in terms of normalization I will be challenged due to transitive dependency. However, for this particular model it is a choice, that we choose to add such transitive dependency for a reason, and I am wondering how you would go about dealing with such cases in the aspect of normalization?
Let me show what I mean:
I have a table called UserSubscription that have the following attributes:
id {dbgenerated}
created
user
price
currency
subscriptionid
The values for:
price
currency
Depend on the subscriptionid, which points to a second table Subscription (in which the subscriptionid is a FK reference to this tables PK). One might say why, would I even consider including duplicate values from the Subscription table into the UserSubscription table? Well the reason is that the Subscription might change at any point in time, and for reference we want to store the original value of the subscription in the UserSubscription so that even if it changes we still have the values that the user signed up for originally.
I know from the perspective of normalization, that this transitive dependency I create should be fixed, and ideally I would move the values back into the subscription table, and just not allow the values to be modified, and instead create a new subscription whenever it is necessary.
But ideally I do not want to create new subscriptions every time something needs to change in those that exist, simply because it is expected these change often - following say market competition values. At the same time for every new subscription created any user will have more to choose from.
This also means that if we no longer want to use a subscription, we would need to: Remove it, and Create a new one. This can be fixed by simply Updating, since we will no longer need the old one anyways.
The above is a school project, I just wonder whether it would ever be "ok" in terms of normalization to choose such approach, when I choose to do so by choice, and to reduce the tasks associated with removing and creating new subscriptions when I expect these would change frequently.
why don't you instead create a M:N table (mapping table) USER_SUBSCRIPTION where you will have the relationships between USER and SUBSCRIPTION ? You can store all values there historically, and don't have to remove/create anything with the change.. it the user decides to opt-out, you only update the flag_active, flag_deleted, flag_dtime_end, whatever works for you...
Here is a simple model for demonstration:
USER
id_user PK
name
... other details
SUBSCRIPTION
id_subscription PK
name
details
flag_active (TRUE|FALSE or 1|0 values)
... other details
USER_SUBSCRIPTION
id_user FK
id_subscription FK
dtime_start -- when the subscription started
dtime_end -- when the subscription ended
flag_valid (T|F or 1|0) -- optional, will give you a quick headsup about active subscriptions ... but this is sort of redundant, for you can get it from the dtime_start vs dtime_end .. up to you
This will give you a very generic (and therefore flexibile / scalable) model to work with users' subscriptions ... no duplications, all handled by FK/PK referential constraints, ... etc

CakePHP model association based on field values?

I've got three tables (there's actually several more, but I only need the three for this problem). Applications, Appattrs and Appcats. In CakePHP parlance (as best as I can since I'm still learning the framenwork) Applications hasMany Appattrs and Appattrs belongsTo Applications. Easy.
The problem comes when I want to associate Appattrs and Appcat - the association is predicated on a field value and a corresponding foreign key in Appattrs. For instance:
If appattrs.type = 'appcatid' then appattrs.value would point to a record in the Appcat table.
The appattrs table holds static data appattrs.type='dateadded' and value='201201011300' as well as foreign key references. I'd rather not get into a discussion as to why data is stored this way, I just want to figure out how to create associations that will let me pull an application record, the associated attr records and then an attr record with its associated row from the appropriate table. Dynamically.
It seems to me that I should be able to create a model based on a query and then associate that model - I just can't seem to figure out how to do that.
--
If I need to post schema for the three tables, I can. I can also post my current model code, but honestly, right now it's just association variables so I don't think it'll get anyone anywhere.
Thow I do not understand the logic behind this design, I thing what you are looking for
is Creating and Destroying associations on the fly.
On this section of CakePHP Docs, it describes how you can associate models from within the corresponding controller.
So, for example, when you want to save specific data to Appattr model you can do some data checking and create your association using bind() method.
A very abstract approach to the above would be something like this
public function yourmethod() {
...
if ($this->request->data['Appattr']['type'] == 'sometype') {
$this->Appattr->bindModel(
array(/*Your association*/ => array(/* Your attributes...*/)
);
/* Rest of the logic follows */
}
}
This way you get your job done, but it's very possible to end up having very complicated
data in your database and thus having very complicated code.
I hope this helps

CakePHP need to know model ID before saving

My situation:
I have a form based on several models linked together.
In background I have a routine for saving images , based on ajax call to a particular controller which uploads the image and displays it on a textarea in realtime.
My problem applies when I have to save the record first time, because the ajax routine needs to know the ID of the record to associate the image , but this will be created only after save() will be called.
Is there a way to obtain a ID before the model saves? or some other workaround
Thanks in advance to everyone
Rudy
I think #RichardAtHome provides a good input on this. I'll just add a third solution you may want to consider:
Use universally unique identifiers (UUID) for that model's primary key. CakePHP offers a way to generate them via the String utility. So what you would do is generate the id when the page loads with String::uuid() and then send it as part of the ajax requests.
A couple of solutions:
Change your workflow, so that the images can only be linked to the main model once the main model has been saved (and has an id).
Don't save your images to the database until the main model has been created. You could store them in a session, or just keep appending fields to the end of the form as the user adds more fields.
Option1:
You could try using UUIDs instead of INTs. That way, you can just use CakePHP to generate an ID: String::uuid();.
After you create it, you can populate an id field that would then make the item save with that id.
I'm not sure I'd rely on it's uniqueness if I were doing banking software or something critical, but - it's something like 1 in a billion chance to be duplicate, and for normal websites/software, it seems like that would suffice.
Option2:
In a lot of our projects, we've found it helpful to have a very simple "create a Thing" page with just a title field, then, once saved, it takes them immediately to the more in-depth "edit" page where they can upload files, save extra data...etc etc. Almost like a pg1, pg2. That way, files, as well as any other dynamic ajax-driven data will have an ID to work with.
Probably not, because the ID will be set by the Database, and not CakePhp, and the id can only be known after the database has auto-incremented the ID field...
Not sure how your flow is, but I guess you can save a dummy/empty row to the database without all the associations when the controller gets the page (when $this->data is empty). You know have an ID to an empty row in the database that is like "12, NULL, NULL, NULL, NULL, NULL, ...". You can then store the ID of that dummy record (or do $this->set("id", $id) to pass it to the view to use in your ajax-calls).
After the POST of your page you can then use the ID to store all other information in dummy row, or delete the dummy row from the database when something fails/user navigates away...

Deleting association from HABTM table

I have a Users and a Reports table, connected with HABTM relationship.
I can save a report that creates a correct record in reports_users table, I can view the relationship table by using $this->User->ReportsUser->.., and so on.
Now I want to delete a specific row in reports_users table, but I can't seem to do it.
I have tried the following:
$this->User->ReportsUser->deleteAll(array(
'ReportsUser.report_id' => $this->data['Report']['report_id'],
'ReportsUser.user_id' => $this->data['Report']['user_id']
));
..but it deletes all the rows with the given user_id, with this query:
What am I doing wrong here. Is it a bad data in the deleteAll call?
I don't want to delete any users or reports, only the relationship between them.
I can confirm that the table names are correct, and that the variables exist and are set.
It seems no error with your code. Either your $this->data['ReportUser']['report_id'] is having some problem. The best way is to first try to print your $this->data. And check whether it exists?
For the safety reasons, use the second argument $cascade = false and also unbind all the ReportUser model Associationship using $this->ReportUser->unbindModel() method.
The other reason seems to be deleting data through
$this->User->ReportUser. Try to use $this->ReportUser->deleteAll('your conditions', false); directly.
Please ask if it not worked for you.

Delete a belongsTo Association

There's surprisingly little information to be found about this and I guess I've never run into it before, but I'm in a situation where I have a model with a belongsTo relationship. The model is the key and when I delete a record, I need to ensure that its associated record is also deleted.
Specifically, I have a Building model that belongsTo an Address. When I delete the building, I need to be sure that the associated address is also deleted.
I can't flag the association as dependent, of course, so is a callback the best way to ensure that the address record gets deleted or is there a better way? This is one of those cases where I know I can do it with a callback, but at a visceral level, it seems like there should be a better way. I'm wondering whether that's the case.
Thanks.
why not using Foreign Keys in the database and select on DELETE CASCADE and let the database do the work...
[Based on the comment] if you the Address is attached to other models that you dont want to delete, you can set those FK to ON DELETE RESTRICT and the building wont be deleted.
And if you need something more complex you can add the beforeDelete() callback in your model, there's an example in the doc
Good Luck
In cakephp 3.x you can use the unlink method like described here:
http://api.cakephp.org/3.3/class-Cake.ORM.Association.HasMany.html#_unlink
This would set the foreign key field in the database to null
In your case it should be something like this everytime when you want to delete records from buildings so integrate it in your delete function. Of course it requires properly associations in the models.
$address = $this->Addresses->get('address_id'):
$building = $this->Buildings->find()->where(['building_id' => 'some_id'])->toArray();
if ($this->Addresses->association('Buildings')->unlink($address, $building)) {
$this->Flash->success(__('Success!'));
return $this->redirect($this->referer());
} else {
$this->Flash->error(__('Error.'));
return $this->redirect($this->referer());
}

Resources