I have a class hirarchy :
class Item {}
class Participation extends Item{}
class Contribution extends Participation{}
class Question extends Participation{}
I would like to have a table per class so, I add tablePerHierarchy false in Item
I need a discrimator to implements a query : where class = "Contribution"
I try a lot of implementation but it's not working.
How to do that ?
Thanks
Do you want table per hierarchy or table per class? It's not clear in your question.
With the following domain objects, you can do it either way:
// Item.groovy
class Item {
String x
}
// Participation.groovy
class Participation extends Item {
String y
}
Using the default, table per hierarchy strategy, just one table will be used to store Items and all the subclasses of Items too. The default discriminator column is called class, which grails will use automatically. The schema generated by grails schema-export looks like this:
create table item (
id bigint generated by default as identity (start with 1),
version bigint not null,
x varchar(255) not null,
class varchar(255) not null,
y varchar(255),
primary key (id)
);
There's just one table for both classes which contains all the fields declared in every class in the hierarchy plus the discriminator column class. If you do a query like Participation.list(), the SQL grails generates looks like this:
select
this_.id as id1_0_,
this_.version as version1_0_,
this_.x as x1_0_,
this_.y as y1_0_
from
item this_
where
this_.class='Participation'
By changing the inheritance strategy to table per class with static mapping { tablePerHieracrchy false } in Item.groovy, grails will generate a table for each of classes in the hierarchy. Each table stores only the fields declared in each class, so a Participation object would be represented by a row in both the Item table and the Participation table. The schema looks like this:
create table item (
id bigint generated by default as identity (start with 1),
version bigint not null,
x varchar(255) not null,
primary key (id)
);
create table participation (
id bigint not null,
y varchar(255) not null,
primary key (id)
);
And the SQL for Participation.list() changes to:
select
this_.id as id1_0_,
this_1_.version as version1_0_,
this_1_.x as x1_0_,
this_.y as y2_0_
from
participation this_
inner join
item this_1_
on this_.id=this_1_.id
Related
I have the parent domain of Teacher and child domain of Student ( one to many)
Student have entity of student_certificate which is a byte( upload file to be exact)
my concern here is i want to separate the student_certificate and create another domain of Student_attachment, but the twist i want to do is to lies the student_attachment on the table of Student
is it possible to do?because there is an existing data so creating another table is a risky way
...but the twist i want to do is to lies the student_attachment on the
table of Student
You can use the embedded attribute as shown below:
class Teacher {
String name
static hasMany = [students: Student]
}
class Student {
String name
StudentAttachment certificate
static embedded = ['certificate']
}
class StudentAttachment {
byte[] attachment
}
create table student (id bigint generated by default as identity, version bigint not null, name varchar(255) not null, certificate_attachment binary(255) not null, primary key (id));
create table teacher (id bigint generated by default as identity, version bigint not null, name varchar(255) not null, primary key (id));
create table teacher_student (teacher_students_id bigint not null, student_id bigint);
I Have the following domains in my gorm package:
Domain.groovy
package gorm
class Domain {
String createdBy
static constraints = {
}
static mapping = {
tablePerHierarchy true
}
}
User.groovy
package gorm
class User extends Domain {
String name
static constraints = {
}
}
I want a table named user with the fields of the base class domain, but instead GROM generate a table whit this specification
create table domain
(
id bigint auto_increment
primary key,
version bigint not null,
created_by varchar(255) not null,
class varchar(255) not null,
name varchar(255) null
)
I'm using mysql driver with grails 2.5.6.
It generates the table with the name as domain because you are using tablePerHierarchy true in your mapping.
Basically, you will get one table where you can set different discriminators for the subclasses.
See here for more information on inheritance strategies: http://docs.grails.org/2.5.x/guide/single.html#GORM
(scroll down to: 7.2.3 Inheritance in GORM)
If you simply want schema-export to generate your table with the name as user, then you would need to add the following to your mapping block in the Domain class:
table 'user'
so the entire mapping block would look like:
static mapping = {
table 'user'
tablePerHierarchy true
}
However, this may not make sense to name the table user if you have other classes extend from Domain.
(and if you don't plan to have other classes extend from Domain, then just add your fields into your User domain).
If you want to generate two tables (Domain and User), then set tablePerHierachy false.
Here is a great write-up with examples that may help you decide which way you want to go for your project:
https://sysgears.com/articles/advanced-gorm-features-inheritance-embedded-data-maps-and-lists-storing/
As a side note: I'm not keen on the name Domain for a domain class; it is too generic and may get confusing when you are talking about the specific Domain class vs domain classes. At least name it BaseDomain.
It seems that it is impossible with Grails to map a domain class to a database table that has is a foreign key column named 'interface'.
In my case there's a relationship with two tables INTERFACE and INTERFACE_DETAILS. They are legacy databases and column names can not be changed or added.
Put it simple INTERFACE_DETAILS has an FK column 'INTERFACE' referring to PK INTERFACE.ID
class Interface {
String id;
static mapping = {
table "INTERFACE"
version false
id generator: 'assigned'
id column: 'id', sqlType:"varchar2(20)"
}
class InterfaceDetails {
Interface iface;
static belongsTo = [iface: Interface]
static mapping = {
table "INTERFACE_DETAILS"
version false
id column: 'interface'
iface column: 'INTERFACE', sqlType:"varchar2(20)", insertable: false, updateable: false
}
}
I'm currently using H2 database. When I try to add a row to InterfaceDetails this error occurs:
Referential integrity constraint violation: "FK351396597E3917F9: PUBLIC.INTERFACE_DETAILS FOREIGN KEY(INTERFACE) REFERENCES PUBLIC.INTERFACE(ID)"; SQL statement:
insert into INTERFACE_DETAILS (interface) values (null) [23506-164]
I wonder why hibernate adds null for 'interface' value ?
I do have to have line : "id column: 'interface'" because in other case Hibernate will generate an extra column: "iface_id" and this is not suitable. Existing database columns can not be changed.
This is quite puzzling. Please tell me that Grails can handle this situation.
stripped down schema:
create table interface (id varchar2(20) not null,primary key (id));
create table interface_details (interface varchar2(20) not null, name varchar(255) not null, primary key (interface));
alter table interface_details add constraint FK_INTERFACE foreign key (interface) references interface;
I have a domain class called FoapRequest. I want one of the properties called "approver" to be a list of integers. Order matters, so I've defined the class as described by http://grails.org/doc/latest/guide/GORM.html#sets,ListsAndMaps as a list:
class FoapRequest {
Integer requester
Integer subject
List approver
static hasMany = [foap:FOAP, newFoap:NewFoap, approver:Integer]
...
Just for clarification, FOAP and NewFoap are two other domain objects.
I need to map this class to a particular table in the Oracle database, so I also specify a static mapping with a join table:
static mapping = {
table 'OUR_SCHEMA.FOAP_REQUEST_TABLE
id column : 'ID', generator:'sequence', params: [sequence:'OUR_SCHEMA.FOAP_REQUEST_SEQ']
requester column : 'REQUESTER'
subject column : 'SUBJECT'
approver indexColumn: [name: "APPROVER_IDX"], generator:'sequence', params: [sequence:'OUR_SCHEMA.APPROVER_SEQ'],
joinTable: [name:'OUR_SCHEMA.APPROVER_TABLE',
key: 'ASSOCIATED_REQUEST',
column: 'APPROVER_PIDM',
type: "integer"
]
However, when I try to create a new instance of the FoapRequest object, I get the following error:
Invalid column type
The console displays the following:
Error 2012-08-01 12:29:31,619 [http-bio-8080-exec-9] ERROR errors.GrailsExceptionResolver - SQLException occurred when processing request: [POST] /FOAPauth/foapRequest/saveFoapRequests - parameters:
I am certain that the issue lies with the jointable. The domain model didn't include the joinTable originally- approver was just an Integer type (I realized too late that I was going to need to track multiple approvers).
Here's the SQL for creating the APPROVERS table:
CREATE TABLE "OUR_SCHEMA"."APPROVER_TABLE"
(
"APPROVER_IDX" NUMBER(*,0) NOT NULL ENABLE,
"ASSOCIATED_REQUEST" NUMBER(*,0) NOT NULL ENABLE,
"APPROVER_PIDM" NUMBER(8),
);
I'd prefer to avoid creating an Approver domain class if at all possible, since all I really need to keep track of are the integer identifiers.
So, after much janking with join tables, I determined that the best way to deal with my needs was to simply create an Approver object in my domain model.
class Approver {
Integer pidm
String approvalDecision
Date lastUpdated
Date dateCreated
static belongsTo = [foap: FOAP]
}
To be honest, I'm not really sure why I was trying so hard to avoid this. Possibly because my DBAs use a version control system for table definitions that I find a hair annoying. :)
Regardless, a simple one-to-many relationship between domain classes met all my needs, no join table required.
For those who are still burning to know, I did manage to get a statically mapped join table working using a Map, which was more appropriate for my needs (though not as appropriate for them as a new domain class, and not nearly as simple).
I ended up doing it in a different domain object- FOAP instead of FoapRequest:
import java.util.Map
class FOAP {
...
Map approvalData
...
static mapping = {
table 'OURSCHEMA.FOAP_TABLE'
id column : 'ID', generator:'jpl.hibernate.util.TriggerAssignedIdentityGenerator'
fund column : 'FUND'
org column : 'ORG'
chartOfAccounts column : 'CHART_OF_ACCOUNTS'
permissionType column: 'PERMISSION_TYPE'
foapRequest column: 'REQUEST_ID'
version column : 'VERSION'
approvalData joinTable: [name:'OURSCHEMA.FOAP_APPROVERS',
key: 'FOAP'
]
}
For the table definition, I used the column names similar to those in my original question.
CREATE TABLE "OUR_SCHEMA"."APPROVER_TABLE"
(
"FOAP" NUMBER(*,0) NOT NULL ENABLE,
"APPROVER_IDX" VARCHAR2(255),
"APPROVER_DLT" NUMBER(8),
);
The IDX column was the map object's key, the DLT column its value. I'd recommend against this approach, for anyone who can avoid it. Creating a new domain object is much simpler.
I'm newbie to Yii. Official documentation does not give any examples for CDbMessageSource.
Questions:
1) How/Where do I set CDbMessageSource as my MessageSource ?
2) In my current application I store Categories in one table and translations for Categories in other table. Tables structure:
CATEGORY
----------
cat_id (PK)
CATEGORY_TRANSLATION
--------------------
cat_id (FK)
en
ru
Now if I introduce sub-categories I would model DB this way:
SUB_CATEGORY
------------
sub_cat_id (PK)
cat_id (FK)
SUB_CATEGORY_TRANSLATION
------------------------
sub_cat_id (FK)
en
ru
Do I understand it correctly that in Yii if I want to use CDbMessageSource to store translations then I would need to merge CATEGORY & SUB_CATEGORY in to one table , then merge CATEGORY_TRANSLATION & SUB_CATEGORY_TRANSLATION in to other so that in result I get following structure (taken from here http://www.yiiframework.com/doc/api/1.1/CDbMessageSource) :
CREATE TABLE SourceMessage
(
id INTEGER PRIMARY KEY,
category VARCHAR(32),
message TEXT
);
CREATE TABLE Message
(
id INTEGER,
language VARCHAR(16),
translation TEXT,
PRIMARY KEY (id, language),
CONSTRAINT FK_Message_SourceMessage FOREIGN KEY (id)
REFERENCES SourceMessage (id) ON DELETE CASCADE ON UPDATE RESTRICT
);
Thank you !
How to enable CDbMessageSource
The message source is an application component with the name "messages". Therefore you configure it just like any other component in your application configuration file:
array(
......
'components'=>array(
......
'messages'=>array(
'class'=>'CDbMessageSource',
// additional parameters for CDbMessageSource here
),
),
),
)
The message source and localizable models -- not an ideal relationship
It's important to keep in mind that the message source only provides translations for known messages. It does not make much sense to involve the message source in your model localization because how would you utilize it?
Assume you have a category with id = 1. How would you get its localized title? Something like Yii::t('category', 'title_'.$category->id) could work, but it's somewhat clumsy (not desirable syntax, you have to "bake in" your primary key information into your display code, etc). If your title localizations are also meant to be modifiable by users this is going to get even more complicated. (In any case, if you wanted to do this then merging the two translation tables and using a separate value when populating SourceMessage.category would be the way to go).
An alternative approach to localizing models
Here's a brief rundown of how you can conveniently localize your models. Let's say we have a Room model with a localizable name property. You can create a new table named LocalizedString and the corresponding model that has a structure similar to this:
CREATE TABLE IF NOT EXISTS `localized_string` (
`Id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`LocaleCode` char(5) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
`StringTemplate` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`Id`,`LocaleCode`),
);
Then, configure your Room model with a relation on LocalizedString:
public function relations()
{
$localeCode = Yii::app()->getLanguage();
return array(
'nameStringTemplate' => array(
self::BELONGS_TO, 'LocalizedString', 'NameStringTemplateId',
'condition' => 'nameStringTemplate.LocaleCode = \''.$localeCode.'\''),
);
}
And add a read-only property:
public function getName() {
return $this->nameStringTemplate->StringTemplate;
}
By doing this, you can now write $room->name anywhere and you will automagically get back the localized translation for the application's current language.
There are many details that need to be taken care of and that I have glossed over here, but the idea should be apparent.