I have two domain classes. One is :
class User {
String login
String password
String firstName
String lastName
String address
String email
static constraints = {
login blank:false, size:5..15,matches:/[\S]+/, unique:true
password blank:false, size:5..15,matches:/[\S]+/
firstName blank:false
lastName blank:false
email email: true
}
}
And other is
class AddWebsite {
String website
User user
static constraints = {
website blank:false
website(unique: ['user'])
}
}
I am working with MongoDB at the backend. I need that for a particular login value, all siteURL values should be unique. Ex: login = abc#gmail.com. Then this user can have all unique url only in the database. But same urls can exist for different users. How do I do that using the unique constraint or any other approach?
Use embedded sub-documents to store SiteURL instances right inside the User. Then you define the collection to be a Set, which makes sure, all it's entries are unique. If you want to use the default mongo collection types or want to persist the order, define an interceptor like:
def beforeSave = {
urls = urls.unique()
}
UPDATE:
If your urls are plain strings, use the default primitive collection (no hasMany):
class User {
String login
//...
Set urls = new HashSet()
}
In this case you should be able to place unique constraint on the AddWebsite domain class such as this:
class AddWebsite {
String website
User user
static constraints = {
website(blank:false, unique: ['user'])
}
}
This will ensure that each website is unique in the database per user. Notice that multiple constraints are applied to the property website.
edited to match updated question.
It finally worked. I was getting the user cannot be null error while entering the website though it was not being validated in the AddWebsite domain class. I made the following changes and got it to work:
class AddWebsite{
String website
User user
static belongsTo = [user: User]
static constraints = {
website( url:true, unique: ['user'])
}
}
And in my controller also, I set the value of the user object to the session variable:
def addWebsites() {
if(request.method == 'POST') {
def w = new AddWebsite()
w.properties[
'website'
] = params
w.user = session["user"] //modified to make it work
if(w.save()) {
render view:'addWebsites', model:[message: "Successfully saved"]
}
else {
return [addWebsite:w]
}
}
Hope it helps someone :)
Related
I have a User model which I want to add a friends property to. Friends, are supposed to be other Users.
I created the UserFriendsPivot:
final class UserFriendsPivot: MySQLPivot, ModifiablePivot {
var id: Int?
var userID: User.ID
var friendID: User.ID
typealias Left = User
typealias Right = User
static var leftIDKey: WritableKeyPath<UserFriendsPivot, Int> {
return \.userID
}
static var rightIDKey: WritableKeyPath<UserFriendsPivot, Int> {
return \.friendID
}
init(_ user: User, _ friend: User) throws {
self.userID = try user .requireID()
self.friendID = try friend.requireID()
}
}
extension UserFriendsPivot: Migration {
public static var entity: String {
return "user_friends"
}
}
I added the friends property to User:
var friends: Siblings<User, User, UserFriendsPivot> {
return siblings()
}
Now, I'm seeing the following error on the line with return siblings():
Ambiguous use of 'siblings(related:through:)'
I tried to replace it with:
return siblings(related: User.self, through: UserFriendsPivot.self)
...without any luck.
I know that the two code snippets should work, because I straight-up copied them from another siblings relationship I built between Event and User that is working just fine.
The only difference I'm seeing is that I'm trying to build a relationship between the same models.
What can I do?
Try replacing your friends definition with something like:
var friends: Siblings<User,UserFriendsPivot.Right, UserFriendsPivot> {
return User.siblings()
}
EDIT:
It ought to work with Left and Right as the same table, but seems to fail because the aliases resolve to the base values. I.e. autocomplete in Xcode shows all the candidates for siblings all end up being of type:
Siblings<User, User, UserFriendsPivot> siblings(...)
Instead of:
Siblings<User, UserFriendsPivot.Right, UserFriendsPivot> siblings(...)
and similar.
I'd suggest raising a bug on GitHub. In the meantime, how about creating a copy of User with a different name and setting:
static let entity = "User"
to use the same physical table. Not pretty, but it might get you working.
The issue here is that in a same-Model (User-User) siblings relation, Fluent cannot infer which sibling you are referring to – the sides need to be specified.
extension User {
// friends of this user
var friends: Siblings<User, User, UserFriendsPivot> {
siblings(UserFriendsPivot.leftIDKey, UserFriendsPivot.rightIDKey)
}
// users who are friends with this user
var friendOf: Siblings<User, User, UserFriendsPivot> {
siblings(UserFriendsPivot.rightIDKey, UserFriendsPivot.leftIDKey)
}
}
The other same-Model consequence is that you will not be able to use the attach convenience method to add to the pivot table, and need to manually create instead:
let pivot = try UserFriendsPivot(user, friend)
pivot.save(on: req)
(There are other approaches to work around this, I just find these straightforward ways above the easiest to use. Specifying the sides and reversing the key positions to obtain the inverse relation are the important concepts.)
as answered by grundoon
I have following entities:
abstract class User
{
string Id
string Name
}
class UserA: User
{
string PropA
}
class UserB : User
{
string PropB
}
It is a good solution to have a unique create (post) with a dynamic parameter and instantiate the subclasses according to a property?
[HttpPost]
public IActionResult Create([FromBody]dynamic data)
{
if (data.PROP == null)
{
_context.Users.Add(new UserA(data.PropA));
}
else
{
_context.Users.Add(new UserB(data.PropB));
}
...
Don't use dynamic. I'm actually kind of surprised that works at all. Though there's no indication that you've actually tested this code yet, so perhaps it doesn't. The modelbinder needs to know a concrete type to bind to, so that it can determine how to map the values onto the destination instance. Without strong types, it can't do anything but make everything a string, since that is how it comes in the request body.
Anyways, for something like this, the correct approach is to use a view model. Your view model should contain all the properties for all the various possible derived types. Again, the modelbinder needs these to determine how to map the data from the request body over, so if a property doesn't exist, it will simply discard the associated data.
This is also why you cannot simply use the base class. If this were a normal method, you could do something like:
public IActionResult Create([FromBody]User data)
Then, inside, you could use pattern matching or similar to cast to the correct derived type. This works because ultimately, the object in memory would actually be an instance of something like UserA, and you're simply up-casting it to User. As a result, you can always cast it back to UserA. However, actions are different. What's coming in from the request is not an object instance. The modelbinder serves to create an object instance out of it, by inspecting the parameter it needs to bind to. If that parameter is of type User, then it will fill the properties on User, and discard everything else. As a result, the object in memory is just User, and there's no way to cast to something like UserA - at least in terms of having all the values that were actually posted for an instance of UserA being on the object.
Which brings us back to the view model:
public class UserViewModel
{
public string Id { get; set; }
public string Name { get; set; }
public string PropA { get; set; }
public string PropB { get; set; }
}
Then, have your action accept that as a param:
public IActionResult Create([FromBody]UserViewModel data)
Then, inside:
if (!string.IsNullOrWhiteSpace(data.PropA))
{
// UserA was posted, map data to an instance of UserA
}
Similarly for UserB. If you like, you could also post an explicit "type" along with the data and switch on that to instantiate the right type. It's up to you. To reduce code duplication, you can instantiate the right type, but store it in an variable of type User. Then, if you need to get back at the correct type, you can use pattern matching:
User user;
switch (data.Type)
{
case "UserA":
user = new UserA
{
Id = data.Id,
Name = data.Name,
PropA = data.PropA
};
break;
// etc.
default:
user = new User
{
Id = data.Id,
Name = data.Name
};
break;
}
Then later:
switch (user)
{
case UserA userA:
// do something specific with `userA`
// etc.
}
Or:
if (user is UserA userA)
{
// do something with `userA`
}
I am using grails 3 rest-api profile. I created generated simple domain class
#Resource(readOnly = false, formats = ['json', 'xml'])
class User {
String name
String address
Date dateOfBirth
}
while http://localhost:8080/user/ will give me back json list of users, if I try http://localhost:8080/user/1 if gives back :
{"message":"Not Found","error":404}
what gives ?
actually I since found out that the url exposed is /user/show/1
since RestResource automatically exposes a controller based on grails.rest.RestfulController you can look in that class for the full list of methods
seems to be a bug. If you specify a uri it works:
#Resource(uri='/user/', readOnly = false, formats = ['json', 'xml'])
class User {
String name
String address
Date dateOfBirth
}
Here is the domain class:
class User {
String name
String email
static constraints = {
name()
email(unique: true)
}
}
The email unique property is being ignored when I call save method, and a duplicated records is created. Am I missing something here?
found the solution:
static mapping = {
email index:true, indexAttributes: [unique:true]
}
I have a form to change email, EmailChangeForm which extends the guard user form, sfGuardUserForm and uses two columns: email_address and password.
I want the form to check if the password is correct and if so, change the email to the new one.
My problem is that the form also saves the password field to the user object.
I know that since the password is checked, it cannot be changed in theory, but I still don't like it being re-saved with the new value from the form, so is there a way to make the form only save the email_address field?
I would suggest a sceleton like this :
class emailForm extends sfFrom {
public function configure(){
$this->widgetSchema['email'] = new sfWidgetFormInputText();
$this->widgetSchema['password'] = new sfWidgetFormInputPassword();
$this->validatorSchema['password'] = new myValidatorPassword();
}
}
class myValidatorPassword extends sfValidatorBase{
protected function doClean($value)
{
$clean = (string) $value;
// current user
$sf_guard_user = sfContext::getInstance()->getUser()->getGuardUser();
if($sf_guard_user)
{
// password is ok?
if ($sf_guard_user->checkPassword($value))
{
return $clean;
}
}
// Throw error
throw new sfValidatorError($this, 'invalid', array('value' => $value));
}
}
So in your action you can easily save the new password :
/***** snip *****/
if($this->form->isValid()){
// set and save new password to current user
$user = $this->getUser()->getGuardUser();
$user->setPassword($formValues["password"]);
$user->save();
/***** snip *****/
Of course this is a basic approach, improvements are always welcome :-)
First make sure you're using the useFields function in your EmailChangeForm class. With that you can define which fields you want to edit with your form (this is better than unset because if you add more fields you dont have to worry with useFields). Example:
$this->useFields(array(
'email'
));
DO NOT INCLUDE THE PASSWORD!
Second: In your template put an extra input field for your password with the same name schema (updatemail[password]).
Third: In your action before the $form->isValid method you add the following:
$params = $request->getParameter($form->getName();
unset($params['password'];
$form->bind($params), $request->getFiles($form->getName()));
if($form->isValid()) {...}
create the new form (editUserForm for example) that extend the base form and then unset the password by this code
class editUserForm extend baseForm{
public function configure(){
unset($this['password']);
}
}
all the name above is an example you must change it to your name.
I'm assuming there is only one field in EmailChangeForm, and that EmailChangeForm extends SfUserForm...
To eliminate all the fields except the email field add this to the configure method:
$this->useFields(array('email'));