Grails Plugins Requiring External Relationships - grails

I posted this on the Grails mailing list yesterday and haven't had any hits. Figured I'd try here as well today.
I'm considering writing a grails plugin but this plugin would require some sort of relationship to an account / user object. However, I don't want to force a particular security model on the plugin. For example, say was writing a comment system plugin (I'm not). I'd have a comment object...
class Comment {
String comment
Date dateCreated
// etc etc
}
The comment is missing a couple of things:
Who added the comment
What the comment was added to.
I'd like to first focus on #1. So someone might be using the Spring security plugin and use the default Person object, or maybe they changed that to User. Who knows. Is there any way that anyone can think of to configure that relationship without hard coding it in the plugin?
One thing I've thought about was to have the grails app extend the plugin's domain classes to add this relationship. so I might do something like...
class ArticleComment extends Comment {
static belongsTo = [user:User]
}
But in a larger plugin, that might be a lot of inheritance requirements. Not the end of the world, but just looking for other possible options.

You can use the same technique employed by the Commentable plugin:
The user of your plugin will need to declare a closure in Config.groovy to evaluate the logged user:
grails.myplugin.user.evaluator = { session.user }
And you can use something like this in your plugin's code to call the user configured closure:
def evaluateUser() {
def evaluator = grailsApplication.config.grails.myplugin.user.evaluator
def user
if(evaluator instanceof Closure) {
evaluator.delegate = this
evaluator.resolveStrategy = Closure.DELEGATE_ONLY
user = evaluator.call()
}
if(!user) {
throw new Exception("No [grails.myplugin.user.evaluator] setting defined or the evaluator doesn't evaluate to an entity. Please define the evaluator correctly in grails-app/conf/Config.groovy")
}
if(!user.id) {
throw new Exception("The evaluated user is not a persistent instance.")
}
return user
}

I think you can do it like SpringSecurity do. Instead of let people extend your Comment class, You can write 2 class CommentUser & CommentPlace; then let others extends them. I think it's more simple.

Related

Set a Grails Domain Class as "No-Insert Mode"

I need to use a complex query on my Grails application. Instead of using a complex criteriaBuilder(), I instead performed the following:
Created View on the database, say ParentChildView.
Mapped it into a domain class.
Use this ParentChildView domain class to perform a .list() operation.
I'm wondering if I can configure this domain class to something like "select-only mode" or "no-insert-allowed mode"?— you know, just to make sure an Exception will be thrown if some developer accidentally tries to insert to this domain.
As per my understanding of your question, you don't want insertion to happen or for sure updates as well.
Your action could be one from these.
User meta-programming and make save method throw an exception for domain. e.g.
User.metaClass.static.save = {
throw new IllegalStateException("Object is not in a state to be save.")
}
You could use hooks if not sure about meta-programming as below.
def beforeInsert() {
throw new IllegalStateException("Object is not in a state to be save.")
}
def beforeUpdate() {
throw new IllegalStateException("Object is not in a state to be updated.")
}
def beforeDelete() {
throw new IllegalStateException("Object is not in a state to be deleted.")
}
Haven't tried mapWith for inserts / updates as it actually don't allow creation of a table but everything like a domain is available.
static mapWith = "none"
Last but not least we could also use transactions but these won't be of that much help. Like in service you could use #Transactional(readOnly=true). But this will just help in services.
Also, you could disable versioning and want cache just only for reads.
static mapping = {
cache usage: 'read-only'
version false
}
I found this topic about read-only domain very helpful and worth.
I'm not sure about third bullet but you could try this as well.
Hope It would help!

Grails: how to retrieve a persistent value for the collection?

I'm writing a Grails application and I need to retrieve a persistent value for the collection of my domain class objects. Let's consider we have got the following domain object:
class UserGroup {
SortedSet<User> users
static hasMany = [ users: User ]
// ...
def beforeUpdate() {
println "BEFORE UPDATE: " + this.getPersistentValue('users');
}
}
class User implements Comparable<User> {
String name
}
And the controller which has the following action:
class UserGroupController {
def addUser() {
UserGroup group = UserGroup.get(params.long('gid'))
User user = User.get(params.long('uid'))
group.addToUsers(user)
group.save(failOnError:true, flush:true)
}
}
The problem is that when beforeUpdate() is called, the users collection already contains the recently added user. So, seems that addTo() method doesn't trigger the beforeUpdate() event.
The same problem occurs when we're talking about isDirty() method. As the changes are applied before the beforeUpdate() is called, the collection is not recognized as dirty field.
Does anyone know how to change this? I'm writing a feature which tracks the history of changes for lots of different object, so I need to have access to the previous value in order to understand whether its value was changed or not.
I have had a similar issue, where things are being updated when I wasn't expecting them when I used the .get() on domain classes. I like to use .read() now because it wont update the database when I'm not expecting it to. Grails does a lot of sneaky things behind the sense which are helpful I think but can be a bit confusing.

Referring to Related Record ID In Controller On Save()

I'm still new to Grails and GORM and I got stumped on this and wasn't able to figure out what I am doing wrong. The intent is to automatically relate the record to the logged in user through the Shiro plugin for Grails.
Class User { static hasMany = [stuff: Stuff] }
Class Stuff { static belongsTo = [user:User] }
Class StuffController {
def create = {
params.put('user', User.createCriteria().get{eq('username',SecurityUtils.subject.principal)}.id)
def stuffInstance = new Stuff(params)
stuffInstance.save()
}
}
I saw in the generate-views version of the create scaffold that the relevant field was referred to as name="user.id", but neither it nor variants (such as user_id) seems to work. The query to the Users domain returns the record id necessary, and params.put in this context seems to correctly append the params object with the new value when I render to a test page (so I'm guessing it's not immutable), but this is what I get from the save():
Property [user] of class [class org.stuffing.Stuff] cannot be null
I've even tried flipping it around and going the other way, with the same result:
User.createCriteria().get{eq('username',SecurityUtils.subject.principal)}
.addToStuff(new Stuff(params))`
.save()
Anyone able to enlighten me on what I'm missing here?
Thanks!
EDIT:
Apparently I was being braindead; I was overriding the "create" method, but the default action is "save" in the _form.gsp template, so it wasn't executing that branch.
On the plus side, I did learn about dynamic finders via Burt below, so it wasn't a total wash.
Thanks for your time, guys!
Your code can be a lot cleaner - there's no reason to use createCriteria here. If you're searching by username, use a dynamic finder:
def stuffInstance = new Stuff(params)
def user = User.findByUsername(SecurityUtils.subject.principal)
stuffInstance.user = user
if (!stuffInstance.save()) {
// inspect stuffInstance.errors
}

Do I have to use webflows?

I want to collect the data for a domain class over a few forms. I'd like to initialise an instance of the domain, carry it through the form pages (assigning the collected data to the properties of the instance) and save the instance after the last form is completed successfully.
Is there a way to do this without webflows?
Is there a way to do this without webflows?
You can use hidden fields to accomplish this. But I may prefer you to use Webflows.
Here are some advantages of using Webflows:
1)You got two new scopes flow and conversation allows you to store variables, which are accessed within your flow
2)You have simple DSL to keep things tidy
3)Since there is a flow scope, you can do something like this:
flow.someThing = new YourClassName(params) //places object in flow scope
Keep in Mind:
1)If you use flow-scoped objects your class need to be implemented Serializable class.
2)And from Grails 1.2, you need to install Webflow plugin explicitly. Document says this:
From Grails 1.2 onwards Webflow is no longer in Grails core, so you
must install the Webflow plugin to use this feature: grails
install-plugin webflow
(see here).
As an alternative to Ant's comment, you could use the session, but storing a non-domain object or a simple Map. This will definitely lead to a lot of extra complexity, and the webflows do provide a lot of protection against accidental back-buttons, etc.
Rough idea:
in grails-app/domain
class Widget {
String name
int id
// constraints, etc
}
in grails-app/controllers
class WidgetCommand {
// setup your command
}
class WidgetController {
def savePage1 = { WidgetCommand cmd ->
// validate, etc
def widget = session.tempWidget ?: [:]
widget.putAll(cmd.properties)
session.tempWidget = widget
[widget: widget]
}
def savePage2 = { WidgetCommand cmd ->
// etc
}
def savePage3 = {
// or something similar here
def finalWidget = new Widget(session.tempWidget)
finalWidget.save()
}
}
You could try storing an actual domain object directly in memory, but I believe that will automatically be saved at session close if you are editing the object (as opposed to new ones), and you'll have to re-link it to the Hibernate session.

symfony 1.4 - inject current logged in user into Doctrine model

I would like to inject, with full testability, the id of the current logged in user into a creator_id field of a Doctrine_Record class, without using the sfContext singleton. I found a couple of Doctrine behaviours like http://www.symfony-project.org/plugins/sfDoctrineActAsSignablePlugin, but they access the sfContext singleton in a listener or (in another example I found) in the save method of a subclass of Doctrine_Record.
I want to find a single entry point in the symfony controller, where I can inject the current logged in user and keep it till the end of the request.
How do I do?
Hope I have been clear enough.
EDIT:
As #jeremy suggested, I made a custom filter:
class userFilter extends sfFilter
{
public function execute($filterChain)
{
$user = $this->getContext()->getUser();
if ($user->isAuthenticated()) {
$guard = $user->getGuardUser();
Doctrine_Manager::getInstance()->setAttribute('logged_in_user', $guard->getId());
}
// Execute next filter
$filterChain->execute();
}
}
So now my tests and my tasks are sfContext free, I just have to set the proper user Id at some point before starting the interactions with the db:
Doctrine_Manager::getInstance()->setAttribute('logged_in_user', sfConfig::get('default_user'));
Great question! The use of sfContext everywhere is one of the uglier aspects of Symfony 1 and setting it on Doctrine_Manager sounds like a solid idea.
It's probably best to do this either in the configureDoctrine callback that happens in ProjectConfiguration (see below). I'm not 100% a user is present at this point, if it's not, your best bet is a custom filter (reference).
public function configureDoctrine(Doctrine_Manager $manager)
{
$manager->setParam('user_id', sfContext::getInstance()->getUser()->getGuardUser()->getId());
}
Thanks for raising this question. I wrote the http://www.symfony-project.org/plugins/sfAuditablePlugin with this in mind. I basically externalized the method that returns the userId so you can get the userId from session, doctrine parameter or wherever you have it.
Here are the settings to configure the method that returns an array containing user_id and user_name
# Retrieve User Array from custom implementation
# [0] => (int)user_id; [1] => (string)user_name;
getUserArray:
class_name: Utility
static_method_name: getUserAuditArray

Resources