Este documento fornece informações sobre:
1) Linguagem dinâmica Groovy, arquitetura MVC, mapeamento objeto-relacional Hibernate e convenção sobre configuração no Grails;
2) Princípios do Grails como DRY, YAGNI e KISS;
3) Grails como uma plataforma full-stack que fornece servidor web e banco de dados.
1. Linguagem dinâmica • Groovy
Arquitetura • MVC
Mapeamento Objeto Relacional • Hibernate
Convenção • Sobre configuração
Grails DRY • Dont Repeat Yourself
YAGNI • You aint gonna need it
KISS • Keep It Simple, Stupid
Simplicidade e Poder Plataforma
Não apenas um framework
Um ambiente full-stack
Servidor web
Banco de dados
2. Criando um projeto
Instalando e testando 6 passos na linha de comando
Exige Java 5.0 ou superior grails createApp nomeProjeto
Comando para criar o projeto
Download Grails:
cd nomeProjeto
http://grails.org/Download Navegar até o diretório da nova aplicação
Descompacte grails createController nomeControlador
Criar nosso primeiro controlador
Cria uma variável de ambiente
GRAILS_HOME Escrever algum código no melhor estilo hello...
grails testApp
Adicione GRAILS_HOMEbin ao seu PATH Escrever o código de teste e executar os testes
Valide a instalação grails runApp
Comando para executar a aplicação
grails
Acessar a pasta do projeto
cd projeto
3. grails createController Loja
Criação de 2 classes
Loja Controller.groovy
No diretório controllers/jctunes
Classe de teste
render
Exibindo uma mensagem Um dos métodos implícitos
Para todo controlador, por default package projeto
Grails cria uma ação chamada index
class LojaController {
package projeto
def index = {
render "Seja benvindo a Loja"
class LojaController {
}
def index = { } }
}
Por convenção tenta renderizar
grails-app/views/loja/index.gsp
4. Testando o código
com Grails Escrevendo o código de testes
package projeto
Testes de import grails.test.*
Testes unitários
integração class LojaControllerTests extends ControllerUnitTestCase {
protected void setUp() {
super.setUp()
Para todo o ambiente }
desenvolvido incluindo a protected void tearDown() {
base de dados Rápidos super.tearDown()
}
void testPaginaPrincipal() {
Geralmente, mais lentos controller.index()
assertEquals "Seja benvindo a Loja",
controller.response.contentAsString
Fazem uso }
Aplicação deve possuir de mocks e stubs
}
alguma completude
grails test-app
6. Porta em Conflito???
Iniciando Grails
grails –Dserver.port=8087 runApp
CRUD, Scaffolding e Domínio
Scaffolding
Permite gerar
rapidamente interfaces
de CRUD para classes
de domínio
Facilita o
entendimento de como
a Visão e o Controle
interagem no Grails
7. Classes de Domínio Classes de Domínio
Lógica do negócio
São persistidas
Mapeadas automaticamente para tabelas de
banco de dados
Hibernate
Localização
grails-appdomainnomeDoProjeto
Criação
grails createDomainClass
grails createDomainClass Musica
8. grails createDomainClass Musica Again...
package projeto
class Musica {
static constraints = {
}
}
grails createDomainClass Musica Scaffolding
package projeto
class Musica {
Dinâmicos Estáticos
String titulo
• Lógica do • Criação de
String artista controle e templates
Integer duracao visões são
Date lancamento geradas em
static constraints = { tempo de
} execução
}
9. grails createController
Musica
package projeto
class MusicaController {
def index = {
}
}
Habilitando
o Scaffolding dinâmico
package jcTunes
class MusicaController {
def scaffold = Musica
}
10. Vamos testar... Relacionamentos com classes
1 para 1
1 para muitos
Muitos para 1
Muitos para muitos
...
Nova classe
* 1
11. Como definir uma relação
1 para muitos? Classe Album
package projeto
class Album {
String titulo
static hasMany = [musicas: Musica]
static hasMany = [musica: Musica]
static constraints = {
}
}
Altere a classe Musica
static hasMany = [musica: Musica] para tornar a relação bi-direcional
package jctunes
Esta relação é unidirecional
class Musica {
Para torná-la bidirecional inclua uma ...
referência de Album na classe Musica Album album
...
}
12. AlbumController
package jctunes
class AlbumController {
def scaffold = Album
}
Scaffolding estático
Geração do código das visões e dos
controles
Pode ajudar na familiarização com o
Grails e como o todo trabalha junto
Scaffolding Estático Ponto de partida para adicionar
para casos de uso que precisam de
uma lógica além CRUD
13. Scaffolding estático
3 targets básicos
grails • Gera as visões para uma
generateViews classe de domínio específico
grails • Gera o controle para uma
generateController classe de domínio específico
grails • Gera ambos controle e
generateAll visões associados
Vamos examinar
Grails fará 2 perguntas o código gerado
Como o controle já existe
ele pergunta se você deseja sobrescrever a classe Milhares de linhas?
AlbumController e sua classe de teste -> responda Y (yes)
10 camadas???
• DAO
• Controle
• Classe de Negócio
• TO
• POJO
14. Em tempo de execução
Repita o processo
para a classe MUsica
Gere o controlador de Musica novamente
grails generateController projeto.Musica
Gere as visões novamente
grails generateViews projeto.Musica
15. Relacionamentos
class Carro {
Motor motor
}
class Motor {
Relacionamentos Carro carro
}
Não há relação de pertença
Relacionamentos Outra forma
class Carro { class Carro {
Motor motor Motor motor
} }
class Motor { class Motor {
static belongsTo = [carro:Carro] static belongsTo = Carro
} }
16. Qual a diferença? belongsTo
static belongsTo = [carro:Carro] A propriedade que pertence sofrerá
A classe Motor tem sua própria referência para exclusões em cascata
carro
Grails saberá que o Motor pertence a um
static belongsTo = Carro Carro
A classe Motor não possui referência para carro então sempre que um Carro for excluído do
banco, o Motor será excluído em conjunto
Outra classe Classe Artista
Artista class Artista {
Propriedades String nome
nome
albuns
static hasMany = [albuns:Album]
Album pertencerá a um Artista
Com referência }
Música pertencerá a um Album
Sem referencia
17. Classe Album Musica
class Album { class Musica{
String titulo
String titulo
Integer duracao
static hasMany = [musicas:Musica]
Date lancamento
static belongsTo = [artista:Artista]
} static belongsTo = Album
}
Classe Artista
class Artista {
String nome
static hasMany = Classes de Domínio
[albuns:Album, instrumentos:Instrumento]
}
18. Classes de Domínio Validando classes de domínio
Por default, todos os campos de uma classe de Toda aplicação tem regras de negócios que
domínio são persistidas no banco de dados restringem os valores válidos de uma
Para tipos simples como Strings e Integers, propriedade de classe
cada propriedade da classe será mapeada para Idade não pode ser < 0
uma coluna de uma tabela Email deve possuir @ e .
Para tipos complexos serão necessários tabelas O número de um cartão de crédito deve seguir
Toda classe mapeada para uma tabela receberá um padrão
um campo adicional, o id, que será a chave Regras como estas deve estar em um local
primária específico
Validando
as propriedades da Musica Constraints
class Musica { blank notEqual
String titulo
creditCard nullable
String artista
Integer duracao
email range
Date lancamento inList scale
matches size
static constraints = { max unique
titulo(blank: false,minSize:2)
maxSize url
artista(blank: false)
min validator
duracao(min:1)
}
minSize
}
OBS: A validação ocorre quando o método save de um objeto é chamadov
19. Propriedades Transientes Exemplos
Por default, toda propriedade de uma classe package projeto
de domínio é persistida no banco de dados
class Album {
String titulo
E quando existirem propriedades que não
necessitam ser persistidas? String nomeFalso
static transients = [‘nomeFalso’]
static transients
...
}
Outro exemplo
class Pessoa {
BigDecimal saldo
BigDecimal limiteEspecial
BigDecimal getSaldoDisponivel () {
saldo+limiteEspecial mockDomain()
} Testando a classe de domínio
static transients = [‘saldoDisponivel’]
...
}
20. Testando uma classe de domínio
mockDomain
void testDuracaoMinima() {
mockDomain(Musica) Entendendo
def musica = new Musica(duracao: 0) os Controles
assertFalse 'Validacao deve falhar', musica.validate()
assertEquals "min", musica.errors.duracao
}
Introdução Introdução
Os controles são classes responsáveis por Existe um controle para cada requisição
manipular requisições que chegam à
aplicação
O controle recebe a requisição Não precisam herdar ou implementar uma
Geralmente, realiza alguma tarefa com a
interface específica
requisição
E finalmente decide o que deve acontecer em
seguida
Executar uma outra ação dele ou de outro controle
Renderizar uma visão
Renderizar informações diretamente para uma visão
21. Definindo um controle Ações de um controle
Localização class Ações são definidas
SampleController { como um campo
grails-app/controllers
def first = {...} Cada campo é atribuído
Convenção def second = {...} a bloco de código
O nome da classe deve terminar com “Controller” def third = {...} utilizando uma closure
def fourth = {...} Groovy
} Controller pode definir
um número qualquer de
ações
Mapeamentos, urls no Grails Configurando a ação default
class SampleController { Se o controle
def first = {...} Tem apenas 1 ação
... Tal ação torna-se a default
}
A primeira parte de uma url representa qual controle Possui uma ação chamada index
está sendo acessado A ação index será a default
A segunda parte representa a ação a ser executada
Define uma propriedade defaultAction
nomeAplicacao/sample/first Seu valor será a ação default
22. Controle Controle
Tem apenas 1 ação Possui uma ação chamada index
class SampleController {
class SampleController { def list = {}
def list = {} def index = {}
} }
Controle
com propriedade defaultAction Objetos implícitos no controle
actionName • Nome da ação que está sendo executada
actionUri • Uri relativa a ação em execução
controllerName • Nome do atual controle em execução
class SampleController { controllerUri • Uri do controle em execução
def defaultAction = 'list' flash • Objeto para acessar o escopo flash
log • Instância de org.apache.commons.logging.Log
def list = {}
params • Mapa de parâmetros de requisição
def index = {} request • O objeto HttpServletRequest
} response • O objeto HttpServletResponse
session • O objeto HttpSession object
servletContext • O objeto ServletContext
23. Escopos dos controles Escopos dos controle
request
Objetos colocados dentro de request estarão disponíveis
durante a execução da requisição corrente
flash
Objetos colocados dentro de flash estarão disponíveis
para a requisição atual e para a próxima
session
Objetos colocados no session serão mantidos até que a
sessão do usuário seja invalidada, ou manualmente ou
por expiração
servletContext
Objetos colocados no servletContext são compartilhados
por toda a aplicação e mantidos durante todo o tempo de
vida da aplicação
Acessando parâmetros da Acessando parâmetros da
requisição requisição
Via propriedade params do Grails
def userName = params.userName
log.info("User Name: ${userName}")
Valores da página
=> parâmetros do request
24. Redirect
Redirecionando a requisição Redirecionando a requisição
class SampleController {
def first = {
// redireciona para a acao “second”
Todo controle tem redirect(action: "second")
Às vezes é necessário
acesso a um método }
redirecionar o
chamado redirect,
controle para outra def second = {
que recebe um mapa
ação de controle // ...
como argumento
}
}
Redirecionando para outro
controle Argumentos do redirect
class SampleController {
def first = {
action controller id
//redirecionar para a acao list de Loja • O nome da ação • O nome do controle • Valor do parâmetro
redirect(action: "list", controller: "Loja") para para id para ser enviado
redirecionamento redirecionamento no redirecionamento
}
}
params uri url
• Um mapa de • Um endereço • Um endereço
parâmetros relativo para absoluto
redirecionar
25. Criando um modelo
Uma das atividades fundamentais do controle
Reunir dados para serem renderizados na visão
O controle pode realizar esta tarefa
Ou delegar para uma classe de serviço ou
Informações que chegam às visões
outros componentes
O Model
Criando um modelo Retornando dados
Quando o controle faz esta tarefa, os dados class MusicaController {
são disponibilizados diretamente para as def show = {
visões através de um mapa, o MODEL [ musica: Musica.get(params.id) ]
}
O mapa é retornado pelas ações de um }
controle return é opcional
Como a última expressão avaliada pela ação foi um mapa,
então o mapa é o valor retornado desta ação
O mapa pode ter qualquer quantidade de valores
26. Renderizando E se eu quiser
a Visão alterar a View de destino?
O controle MusicaController Utilize o método render
1 método chamado show
Retorna um modelo contendo uma chave e um objeto class MusicaController {
def show = {
render(view:"exibir",
Onde está a referência para qual visão ele irá model:[musica: Musica.get(params.id)])
despachar o fluxo? }
}
Por convenção
Por convenção grails-app/views/musica/exibir.gsp
grails-app/views/musica/show.gsp
Ligando dados da visão
E para mudar o diretório? para o controle
Utilize um caminho absoluto
class MusicaController {
def show = {
render(view:"/outrapasta/exibir",
model:[musica: Musica.get(params.id)])
}
}
grails-app/views/outrapasta/exibir.gsp
27. Ligando dados
Ligando dados primeira forma
Controles também precisam class AlbumController {
Criar novos objetos de domínio def save = {
Popular as propriedades destes objetos com def album = new Album()
valores recebidos como parâmetros na
requisição album.genero = params.genero
album.titulo = params.titulo
album.save()
}
}
class AlbumController {
def save = {
def album = new Album(params)
album.save()
}
}
28. Alterando as propriedades com DataBinding
dataBinding de objetos múltiplos
class AlbumController {
def update= {
def album = Album.get(params.id)
album.properties = params def album = new Album( params["album"] )
def artist = new Artista( params["artista"] )
album.save() }
}
DataBinding e Associações 2 cenários na visão
Na classe de domínio <input type="text" name="artista.name" />
Uma nova instância de artista é criada e atribuída
class Album { à instância do Álbum
Artista artista
<input type="text"
...
name="artista.id" value="1" />,
} Recupera uma instância de um artista existente e
Na classe de controle atribui à classe do Álbum
def album = new Album(params)
29. Uma variação dos cenários
<input type="text”
name="songs[0].title"
value="The Bucket" />
<input type="text
name="songs[1].title" value="Milk" />
<input type="text" name="songs[0].id" GORM
value="23" /> Grails Object Relational Mapping
<input type="text" name="songs[1].id"
value="47" />
get() getAll()
def album = Album.get(params.id) def album = Album.getAll(1,2,3)
Get recebe um id e retorna Recebe
Ou a instância do objeto correspondente ao id Vários identificadores
Ou null caso não exista Retorna
Uma lista de instâncias
30. read() list()
4 atributos
max
def album = Album.read(params.id)
Número máximo de instâncias a serem retornadas
offset
Recebe Qual o 0 relativo da consulta
Um identificador sort
Retorna Nome da propriedade para utilizar no ORDER BY
Um objeto em estado somente leitura order
Se a ordenação é ASC ou DESC
Classe Album list()
class Album { def allAlbums = Album.list()
String titulo Recupera todos os albuns
Date lancamento def top10 = Album.list(
max:10, sort:‘lancamentos', order:'desc')
static hasMany = [musicas:Musica] Retorna os 10 albuns mais recentes
static belongsTo = [artista:Artista] def totalAlbums = Album.count()
} Recupera o total de albuns
31. Classe Album
class Album {
String titulo
Date lancamento
static hasMany = [musicas:Musica]
static belongsTo = [artista:Artista]
}
save()
listOrderBy*() Inserir uma nova instância
def tudoPorLancamento =
Album.listOrderByLancamento() def album = new Album(params)
album.save()
def tudoPorTitulo =
Album.listOrderByTitulo()
32. save() save()
Alterar uma instância Variação
def album = Album.get(1)
album.titulo = “Titulo Alterado“ album.save(insert:true)
album.save()
delete()
Excluir do banco
album.delete()
33. findBy findBy
Retorna uma única instância Retorna uma única instância
Album.findByTituloAndGenero( Album.findByTituloOrGenero(
“RATM”, “Rock”) “RATM”, “Rock”)
Mais findBy Mais findBy
Album.findByLancamentoBetween(hoje-10,hoje) Album.findByGeneroIsNull()
Album.findByTituloEquals(‘RATM') Album.findByGeneroIsNotNull()
Album.findByLancamentoLessThan(ontem)
Album.findByLancamentoGreaterThan(ontem)
Album.findByLancamentoLessThanOrEqual(ontem)
Album.findByLancamentoGreaterThanOrEqual(o
ntem) Album.findByTituloLike(‘O tempo não%')
Album.findByTituloInList([‘123', ‘Brasil']) Album.findByTituloNotEqual('Odelay")
34. Métodos “primos” Criteria
findAllBy* def c = Album.createCriteria()
Retorna uma lista baseado nos parâmetros
def results = c.list {
eq('genero', 'Alternativo')
countBy* between(‘lancamento', new Date()-30, new
Retorna um inteiro com o total de instância que Date())
satisfazem a consulta
}