My problem simply put in Grails: I want a plugin OR example for translations of Domain Classes.
Description: I would like to have translatable entites with a nice DB scheme behind it. For example
Domain Class 1: Book (id, author_id, number_pages)
Translations for Book: (book_id, language, title, description)
Domain Class 2: Author (id, birthday)
Translations for Author (author_id, language, first_name, last_name) (for example, the same author is known under different names in different countries)
What I want to do: knowing the Language, I want to get The book with an ID, and EAGER fetching its author (of course book and author with the right translations). Or for example to search all books by an author, known by a first_name in a country/language.
How would you do manually that with GORM?
Is there a plugin for that or something to hide the "business logic"?
Or how could I simply get all translations for a book?
Whab about just using GORM as is - i.e. Book and BookTranslations would be two domain objects with a one to many translation between Book and BookTranslations, and then you could do BookTranslations.findAllByBook, criteria queries, etc.
The reason I suggest this is that there is an entity relationship in your description (i.e. one to many), so it's not like a parent/child/embedded style class heirarchy would do anything for you. This also gives you complete control over the fetching strategy[1]
I think the only 'secret sauce' here is that you could standardize on the way you represented languages. For example, if you actually had some unicode text in those rows or mappings to a standard messages files, then you could use the ISO standard language names for the language column in the BookTranslation table (e.g. en-US for US English). This would allow you to take direct advantage of the Grails internationalizatino features, where locales and message files can be automatically resolved, plus there are taglibs and other goodies to make internationalization less painful.
See [2]
A simple example would be something like the following (not tested, so pardon any compile errors):
class Book {
String author
int numPages
static hasMany [ bookTranslations: BookTranslations]
}
class BookTranslations {
String language
String title
String description
static belongsTo = Book
}
def book = new Book(author:"test", numPages:20)
book.save()
def translation1 = new BookTranslations(language:"en-us", title:"How to Book", description:"A wonderful book")
book.addToBookTranslations(translation1)
def translation2 = new BookTranslations(language:"en-us", title:"Second How to Book", description:"A glorious book")
book.addToBookTranslations(translation2)
def results = BookTranslations.findAllByBook(book)
// or ..
results = book.bookTranslations
As luck would have it, the example in the GORM chapter in the Grails Documentation is all about authors and books. See [3]
[1] http://grails.org/doc/latest/guide/5.%20Object%20Relational%20Mapping%20%28GORM%29.html#5.5.2.8%20Eager%20and%20Lazy%20Fetching
[2] http://grails.org/doc/latest/guide/10.%20Internationalization.html
[3] http://grails.org/doc/latest/guide/5.%20Object%20Relational%20Mapping%20%28GORM%29.html
Just stumbled upon this plugin, seems like it may be what you are looking for.
Related
I have an Author who has many books. A book has many chapters. The book is published by different publishers who pay different commissions.
So, the domains will be.
Author { hasMany [books: Book] }
Book { hasMany [chapters: Chapter, publishers: Publisher] }
Publisher { }
Grails generates the book_chapter and book_publisher tables. So far so good.
The database looks good.
Commission is a value of the type Single. But where do I put the commission?
Would you show me how to set this up please?
Many Thanks!
I give up. I don't know how to set up the relationship between the Author, Book, and Publisher. The idea is simple. Different publishers will pay me different commissions for my books.
I will remove the hasMany Publisher relationship from the Book. So, my Book is just now:
Author { hasMany [books: Book] }
Book { hasMany [chapters: Chapter] }
Publisher { ... }
Now I will add a Commission domain to have my commission amount stored in the database.
Commission {
Author author
Book book
Publisher publisher
Float amount
}
Next I need to make a composite primary key with both Author, Book, and Publisher. This will prevent duplicated records. It is because this is not correct to have more than one record in the database to show how much the publisher pays me for the same book.
I think this will work. What I need to do next is to program the controller to fill out the commission objects manually.
I originally don't want to think or type too much if I can use the built-in scaffolding. Let Grails do all the heavy lifting for me. I don't think I can use the scaffolding anymore.
Good job me.
Please leave a comment.
I have set up a one-to-many relationship in my scaffolded Grails application:
class Course {
County county
Date date
int maxAttendance
static hasMany = [ persons:Person ]
}
class Person {
String firstName
String lastName
String email
Course course
boolean attended
boolean paid
static belongsTo = [ class:Course ]
}
So, when a user views the CourseController, they are able to see Person's registered in the selected Course.
My question is, how can I change the application so that when a user views the people in a given course, they can also view/modify the checkboxes for 'boolean attended' and 'boolean paid', which are also in the Person domain? Here is a screenshot:
The thing is that you are using the scaffolded view, so you are pretty much stuck with the default design.
You can modify the behaviour, by installing and modifying the templates that grails uses to generate those views:
grails install-templates
This will create the templates in src/templates/scaffolding, although I do not recommend that approach. Maybe it is time that you start developing your own controllers and views, since the scaffolding is there mainly for testing and for administrative use.
I've been struggling to produce the right configurations to produce cascade-delete behaviour in a relatively simple Grails project.
Say I have the following simple domain classes:
class Author {
String name
static constraints = {
}
}
and
class Book {
String title
Author author
static constraints = {
}
}
If I create an author, and then create a book written by that author, I am not able to delete the Author without first manually deleting the book. I get an "Integrity constraint violation". This isn't suprising as MySQL (my underlying database) is created by Grails with a "foreign key constraint" on the "author" column of the "book" table as "Restrict" (and this behaviour is consistent with expectations from the Grails documentation as I understand it).
Now, if I were to manually change the underlying database constraint on the "author" column of the book table from "Restrict" to "Cascade", I get the behaviour I want. Namely, that if you delete the Author, all their books are also deleted.
So, what I'd like to do is change my Grails "Book" class in a way that creates the "book" table with "on delete cascade" on the author column. I've been reading plenty of information about doing this sort of thing and GORM defaults using "belongsTo" and explicit "mappings".
One way to do this, based on the documentation for "belongsTo", seemed to be to change the line in the Book class from:
Author author
to
static belongsTo = [author: Author]
Thereby making it explicit that the Author is the "owning side" of the relationship. The documentation seems to suggest that this should generate the cascade-delete behaviour I'm after. However, it doesn't work unless I add an explicit "hasMany = [books:Book]" into the Author class. I don't wish to do this. (This wish makes more sense in my actual business domain, but even just as an exercise in understanding, I don't yet get why I have to have the Author domain class know about books explicitly).
I'd just like a grails setting to change the Book class to produce the "cascade delete" setting in the database, without having to change the Author class. I tried using an explicit mapping like:
static mapping = {
author cascade: 'all'
}
and combinations of this with other explicit mapping or "belongsTo" options. This didn't work.
Noting that a simple change to the "constraints" in the underlying SQL database provides the behaviour I want, is there a way to get this through Grails? If not, have I misunderstood something fundamental here or am I trying to do something stupid?
I think you are missing the required field of type Book in Author.
Here's the sample code, which is as per the documentation (tested and works)
class Author {
String name
Book book //you are probably missing this field
static constraints = {
}
}
class Book {
String name
static belongsTo = [author: Author]
static constraints = {
}
}
Test case:
#TestFor(Author)
#Mock([Book])
class AuthorTests {
#Test
void testAuthorBookCascades() {
Author a = new Author(name: "Douglas Adams")
Book b = new Book(name: "So Long, and Thanks for all the Fish")
a.book = b
a.save()
assert Author.count() == 1
assert Book.count() == 1
a.delete()
assert Author.count() == 0
assert Book.count() == 0
}
}
As you can see, you need the Book argument in Author. No need for the hasMany or hasOne clause.
My Grails app is using the searchable plugin, which builds on Compass and Lucene to provide search functionality. I have two searchable classes, say Author and Book. I have mapped these classes to the search index, so that only certain fields can be searched.
To perform a search across both classes I simply call
def results = searchableService.search(query)
One of the nice features of doing the search across both class simultaneously, is that the results object includes metadata about number of results included, number of results available, pagination details etc.
I recently added a boolean approved flag to the Book class and I never want unapproved books to appear in the search results. One option is to replace the call above with:
def bookResults = Book.search(query + " approved:1")
def authorResults = Author.search(query)
However, I now need to figure out how to combine the metadata for both results, which is likely to be tricky (particularly pagination).
Is there a way to search across Book and Author with a single query, but only return approved books?
Do you want to be able to find authors or do you want to find books with a given author?
If you want to find books with a given author, you can configure your domain classes in the following way:
class Author {
String name
...
static searchable = {
root false
}
}
this will result in excluding the Author from the searchableService.search(query)-result and you'll find field names like $/Book/Author/name in your index. (use luke to examine your index: http://code.google.com/p/luke/).
You can change the name of those fields by configuring a better prefix in your Book-class:
class Book {
String name
Author author
...
static searchable = {
author component: [prefix: 'author']
}
}
this will change the name of the field in the index to bookauthor.
If you now search with searchableService.search(query), you'll find all books where the name of the book or the name of the author contains the search term. You can even restrict the search to a given author by using the authorname:xyz syntax.
If you really would like to mix the search results, I only know the solution you already mentioned: mixing both results with your own code, but I guess it will be hard to mix the scoring of the hits in a good way.
Update to your response: Here's my pagination code...
.gsp:
<div class="pagination">
<g:paginate total="${resultsTotal}" params="[q: params.q]"/>
</div>
controller:
result = searchableService.search(params.q, params)
[
resultList: result.results,
resultsTotal: result.total
]
So if you just merge the results of your two searches and add the result.totals, this could work for you.
I've created a test app and came to the following solution. maybe it helps...
if the property approved only has the states 0 and 1, the following query will work:
def results = searchableService.search(
"(${query} AND approved:1) OR (${query} -approved:0 -approved:1)"
)
I guess this can be reformulated in a better way if you don't use the QueryParser but the BooleanQueryBuilder.
BTW: if you add a method like
String getType() { "Book" }
and
String getType() { "Author" }
To your domains, you can even configure your search to do it like this
def results = searchableService.search(
"(${query} AND approved:1) OR (${query} AND type:Author)"
)
I recently read this blog entry by Hadi Hariri: That dreaded M in ASP.NET MVC. Hadi surveyed fellow ASP.NET MVC developers about the types of models they use in the applications. One of his survey questions read:
If you bind to Domain Model, how do you deal with extra data such as country list?
And one person wrote select Other and wrote in: "conventions." Following that, Hadi wrote:
I've actually found another way to solve this problem, partially based on conventions.
And then later says:
Maybe we should take the concept of conventions more seriously than just what folders our Views, Controllers and Models reside in. Maybe we should push conventions to the limit and see if we actually reduce this friction.
What does he mean by using "conventions" to solve this problem? I'm not familiar with the term in this context.
Thanks
Convention in this context could mean many things. Loosely it is just a solution to the problem that is not built in but looked up using names.
An example of this already in MVC is that a route to a controller named "Home" is mapped to a class named "HomeController". Extending this idea to the selected country and country list problem you can think of many solutions.
One example would be given a model:
public class UploadModel
{
public string Country {get; set;}
}
We may define options for this model via a convention on the naming where it looks for a model with the word "Options" after its name and then matches the property on the model with a like named property on the options model.
public class UploadModelOptions
{
public IList<string> Country {get;set;}
}
This may not be a great convention but is an acceptable example of what the author may be talking about.