Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
今こそッ、始めようGrailsブートキャンプ!!!! / Grails Bootcamp ...
Search
Kazuki YAMAMOTO
October 23, 2015
Programming
0
320
今こそッ、始めようGrailsブートキャンプ!!!! / Grails Bootcamp for JGGUG
今こそッ、始めようGrailsブートキャンプ!!!!の資料です。
https://jggug.doorkeeper.jp/events/32330
Kazuki YAMAMOTO
October 23, 2015
Tweet
Share
More Decks by Kazuki YAMAMOTO
See All by Kazuki YAMAMOTO
Grails 3で生まれ変わったGrailsの今 / Spring in Summer 2015
yamkazu
1
430
Grails 3でWeb APIを簡単に作ろう!
yamkazu
6
2.2k
Other Decks in Programming
See All in Programming
エラーって何種類あるの?
kajitack
5
310
Go1.25からのGOMAXPROCS
kuro_kurorrr
1
800
Team topologies and the microservice architecture: a synergistic relationship
cer
PRO
0
1k
XSLTで作るBrainfuck処理系
makki_d
0
210
Blazing Fast UI Development with Compose Hot Reload (droidcon New York 2025)
zsmb
1
210
童醫院敏捷轉型的實踐經驗
cclai999
0
190
Webの外へ飛び出せ NativePHPが切り拓くPHPの未来
takuyakatsusa
2
360
来たるべき 8.0 に備えて React 19 新機能と React Router 固有機能の取捨選択とすり合わせを考える
oukayuka
2
860
VS Code Update for GitHub Copilot
74th
1
390
Systèmes distribués, pour le meilleur et pour le pire - BreizhCamp 2025 - Conférence
slecache
0
110
つよそうにふるまい、つよい成果を出すのなら、つよいのかもしれない
irof
1
300
なぜ適用するか、移行して理解するClean Architecture 〜構造を超えて設計を継承する〜 / Why Apply, Migrate and Understand Clean Architecture - Inherit Design Beyond Structure
seike460
PRO
1
690
Featured
See All Featured
Understanding Cognitive Biases in Performance Measurement
bluesmoon
29
1.8k
Six Lessons from altMBA
skipperchong
28
3.8k
Navigating Team Friction
lara
187
15k
Building Flexible Design Systems
yeseniaperezcruz
328
39k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
8
790
Build The Right Thing And Hit Your Dates
maggiecrowley
36
2.8k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
233
17k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
15
1.5k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
44
2.4k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
29
9.5k
Balancing Empowerment & Direction
lara
1
370
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
281
13k
Transcript
ࠓͦ͜ο͡ΊΑ͏ GrailsϒʔτΩϟϯϓ!!! @yamkazu #jggug
ࢁຊ थ • @yamkazu • JGGUG
None
ຊͷ༰ • Grails֓ཁ • Grails 3֓ཁ • GrailsϋϯζΦϯ • Hallo
World • TODOΞϓϦΛ࡞ͬͯΈΑ͏
ࠓΒͳ͍͜ͱ • Groovyࣗମͷઆ໌ • αϯϓϧίʔυͰొ͢ΔGroovyͷίʔυʹ͍ͭͯՄೳͳ͔͗Γัଊ Λ͍Ε͍͖ͯ·͢ • Grailsͷཏతͳઆ໌ ࠓखΛಈ͔ͯ͠งғؾΛ ௫ΜͰΒ͏͜ͱΛ༏ઌ͠·͢ʂ
ࠓͷϋϯζΦϯʹඞཁͳڥ • Java • Grails • IDE or ςΩετΤσΟλ https://github.com/yamkazu/jggug-grails-bootcamp
Λࢀߟʹ४උΛ͓ئ͍͠·͢ʂ
Grailsͱ • Graeme Rocherࢯ͕։ൃ • ϑϧελοΫͷWebϑϨʔϜϫʔΫ • Groovyϕʔε • Ruby
on RailsɺDjangoͱ͍ͬͨϑϨʔϜϫʔΫʹӨڹΛड͚͍ͯΔ • DRYʢDon't Repeat Yourselfʣ= ಉ͡هड़Λ܁Γฦ͞ͳ͍ • CoCʢConvention over Configurationʣ= ઃఆΑΓن • εΩϟϑΥϧσΟϯά • Java EE্Ͱಈ࡞
Grailsͷྺ࢙ 4QSJOH4PVSDFԼ 7.XBSF͕ 4QSJOH4PVSDFΛങऩ
4QSJOH4PVSDF͕ 1JWPUBMԼ 1JWPUBMଔۀ
OCI͕৽͍͠ϗʔϜ http://www.ociweb.com/resources/news/2015/04/15/april-2015-grails-web-framework-finds-home-oci
Grails 3ͷ֓ཁ • Spring BootΛϕʔεʹ࠶ߏங • GradleͰϏϧυγεςϜ͕Ұ৽ • ΞϓϦέʔγϣϯϓϩϑΝΠϧͷՃ •
ίϯτϩʔϥɺυϝΠϯΫϥεͳͲͷ͍ํ΄ͱΜ Ͳมߋͳ͠ • etc
ͱΓ͋͑ͣHello World!
ϓϩδΣΫτͷ࡞ ίϚϯυϥΠϯ͔ΒҎԼΛ࣮ߦ͢Δɻ $ grails create-app sample $ cd sample ࡞ͨ͠ΒIDEAͰಡΈࠐΉɻ
https://github.com/yamkazu/jggug-grails-bootcamp/blob/master/README.md Ͱ࣮ࢪࡁΈͷਓෆཁʂ
ίϯτϩʔϥͷ࡞ $ grails grails> create-controller hello GrailsͷΠϯλϥΫςΟϒϞʔυ͔ΒҎԼΛ࣮ߦ͠ίϯτϩʔϥΛੜ͢Δɻ
ΞΫγϣϯͷ࣮ package sample class HelloController { def index()
{ render 'Hello Grails!' } } IDEA͔Βgrails-app/controllers/sample/HelloController.groovyΛ։͍ͯ ҎԼͷindexΞΫγϣϯΛՃ͢Δɻ https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-hellocontroller-groovy
ΞϓϦέʔγϣϯͷىಈ grails> run-app GrailsͷΠϯλϥΫςΟϒϞʔυ͔ΒҎԼΛ࣮ߦ͠ΞϓϦέʔγϣϯΛىಈ ͢Δɻ ϒϥβͰ http://localhost:8080 ʹΞΫηε͢Δɻ sample.HelloControllerͷϦϯΫΛΫϦοΫ͢Δɻ 'Hello
Grails!'ͱදࣔ͞ΕΔ͜ͱΛ֬ೝ͢Δɻ
ΞϓϦέʔγϣϯͷఀࢭ grails> stop-app GrailsͷΠϯλϥΫςΟϒϞʔυ͔ΒҎԼΛ࣮ߦ͢Δɻ
GrailsΛߏ͢Δཁૉ
ΞʔςΟϑΝΫτ • GrailsʹΑͬͯಛผѻ͍͞ΕΔΦϒδΣΫτͷ͜ͱ • ։ൃऀ͕هड़ͨ͠··ͷίʔυҎ֎ʹɺGrailsʹΑͬͯੵۃతʹػೳ͕Ճ͞ΕΔ • ී௨ͷΫϥεͱ֨ೲσΟϨΫτϦ͕͔Ε͍ͯΔ • grails-appԼ: ΞʔςΟϑΝΫτ
• srcԼ: ී௨ͷΫϥε • ओͳΞʔςΟϑΝΫτͷछྨ υϝΠϯΫϥε • υϝΠϯΫϥε • ίϯτϩʔϥ • αʔϏε • Ϗϡʔ • λάϥΠϒϥϦ
GrailsͷσΟϨΫτϦߏͷ࡞ yourAppProject grails create-appでこの配下の構造が生成される ├── build.gradle Gradleのビルド設定ファイル ├── gradle Gradle
WrapperのJarファイルが格納される ├── gradle.properties Gradleのプロパティファイル ├── gradlew Gradle Wrapperのスクリプトファイル ├── gradlew.bat Gradle Wrapperのスクリプトファイル ├── grails-app Grailsとして特別扱いをするクラス群を格納する │ ├── assets 静的リソースを格納する │ │ ├── images 画像ファイル │ │ ├── javascripts JavaScriptファイル │ │ └── stylesheets CSSファイル │ ├── conf 設定系 │ ├── controllers コントローラ │ ├── domain ドメインクラス │ ├── i18n メッセージバンドル │ ├── init 起動時に必要なクラス │ ├── services サービス │ ├── taglib タグライブラリ │ ├── utils コーデックと呼ばれる特殊クラスを配置する(滅多に使用することはない) │ └── views ビュー(GSPファイル) └── src ├── integration-test Grailsアプリケーションを内部で起動して実行するテスト │ └── groovy ├── main Grailsの特別扱いを必要としない通常のクラスを格納する │ ├── groovy │ └── webapp └── test 純粋なGroovy/Javaプログラムとしてのテストを格納 └── groovy
εΩϟϑΥϧυΛ ମݧͯ͠ΈΑ͏
υϝΠϯΫϥεͷ࡞ grails> create-domain-class book GrailsͷΠϯλϥΫςΟϒϞʔυ͔ΒҎԼΛ࣮ߦ͠υϝΠϯΫϥεΛੜ͢ Δɻ
υϝΠϯΫϥεΛఆٛ͢Δ IDEAͰgrails-app/domain/sample/Book.groovyΛ։͍ͯҎԼΛهड़͢Δɻ package sample class Book { String
title Integer price static constraints = { } } https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-book-groovy
εΩϟϑΥϧυͷ࣮ߦ grails> generate-all sample.Book GrailsͷΠϯλϥΫςΟϒϞʔυ͔ΒҎԼΛ࣮ߦ͠ը໘Λੜ͢Δɻ ͍͔ͭ͘ͷϑΝΠϧ͕ੜ͞ΕΔɻ
ΞϓϦέʔγϣϯͷىಈ grails> run-app GrailsͷΠϯλϥΫςΟϒϞʔυ͔ΒҎԼΛ࣮ߦ͠ΞϓϦέʔγϣϯΛىಈ ͢Δɻ ϒϥβͰ http://localhost:8080 ʹΞΫηε͢Δɻ sample.BookControllerͷϦϯΫΛΫϦοΫ͢Δɻ
ΠϯλϥΫςΟϒϞʔυͷऴྃ grails> exit GrailsͷΠϯλϥΫςΟϒϞʔυ͔ΒҎԼΛ࣮ߦ͠ΠϯλϥΫςΟϒϞʔυ Λऴྃ͢Δɻ ΞϓϦέʔγϣϯ͕ىಈதͷ߹ࣗಈతʹఀࢭ͢Δɻ
TODOΞϓϦΛ࡞ͬͯΈΑ͏
ࠓճ࡞ΔΞϓϦͷΠϝʔδ
ϓϩδΣΫτΛ࡞ ίϚϯυϥΠϯ͔ΒҎԼΛ࣮ߦ͢Δɻ $ grails create-app todo $ cd todo ࡞ͨ͠ΒIDEAͰಡΈࠐΉɻ
IDEAͷจࣈίʔυͷઃఆ WindowsͷํҎԼઃఆ͔ΒจࣈίʔυΛUTF-8ʹઃఆ͍ͯͩ͘͠͞ɻ [File]-[Settings…] [Editor]-[File Encodings]
·ͣυϝΠϯΫϥεΛ࡞Δ
υϝΠϯΫϥε • ͍ΘΏΔϞσϧΛఆٛ͢ΔΫϥε • υϝΠϯΫϥε㲈 Hibernate༻ޠʮΤϯςΟςΟʯ • GORM(Groovy Object Relational
Mappingɺΰʔ ϜɺΰϧϜ) ΛυϝΠϯΫϥεΛհͯ͠ར ༻͢Δ • ೖྗͷ੍ΛఆٛͰ͖Δ • υϝΠϯΫϥεͷఆ͕ٛσʔλϕʔεͷϚοϐϯάఆٛʹͳΔ • Ϋϥε໊ -> ςʔϒϧ໊ • ϓϩύςΟ໊ -> ΧϥϜ໊ • ੍ -> ΧϥϜͷ੍
υϝΠϯΫϥεͷྫ class Person { // プロパティ String name // 氏名
Integer age // 年齢 // 制約 static constraints = { name size: 10..30 // 10〜30文字でなければならない age min: 18 // 18歳以上でなければならない } }
υϝΠϯΫϥεΛੜ͢Δ GrailsͷΠϯλϥΫςΟϒϞʔυ͔ΒҎԼΛ࣮ߦ͢Δɻ $ grails grails> create-domain-class todo ҎԼͷϑΝΠϧ͕ੜ͞ΕΔɻ grails-app/domain/todo/Todo.groovy src/test/groovy/todo/TodoSpec.groovy
υϝΠϯΫϥεΛఆٛ͢Δ IDEAͰgrails-app/domain/todo/Todo.groovyΛ։͍ͯҎԼΛهड़͢Δɻ package todo class Todo { String
content static constraints = { content blank: false, maxSize: 20 } } https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-todo-groovy
ΞϓϦέʔγϣϯΛىಈͯ͠σʔ λϕʔεΛ֬ೝ͢Δ GrailsͷΠϯλϥΫςΟϒϞʔυ͔ΒҎԼΛ࣮ߦ͢Δɻ grails> run-app ϒϥβͰ http://localhost:8080/dbconsole ʹΞΫηε͢Δɻ ҎԼͷઃఆͰʮConnectʯΛΫϦοΫɻ JDBC
URL: jdbc:h2:mem:devDb User Name: sa
υϝΠϯΫϥεΛͬͯΞϓ Ϧέʔγϣϯىಈ࣌ʹσʔλ ϕʔεʹσʔλΛอଘ͢Δ
υϝΠϯϥΫεΛͬͨCURD ૢ࡞ͷجຊ • υϝΠϯΫϥεʹࣗಈతʹՃ͞ΕΔϝιουΛ͏ • อଘɺߋ৽ • domainInstance.save() • আ
• domainInstance.delete() • Ұཡऔಘ • DomainClass.list() • 1݅औಘ • DomainClass.get(id)
CRUDૢ࡞ͷྫ // 新規作成 def person = new Person(name: "山田", age:
20) person.save() def person = new Person(name: "山田", age: 20).save() // 参照 def person = Person.get(1) // IDを指定して取得 def people = Person.list() // 一覧を取得 def people = Person.list(offset: 10, max: 20) // 開始位置、件数を指定して一覧を取得 def people = Person.list(sort: "name", order: "asc") // ソート条件を指定して取得 int personCount = Person.count() // 件数を取得 // 更新 def person = Person.get(1) person.name = "鈴木" person.save() // 削除 def person = Person.get(1) person.delete()
ϒʔτετϥοϓ • GrailsΞϓϦέʔγϣϯͷىಈ࣌ͱऴྃ࣌ʹɺ؆୯ʹҙ ͷॲཧΛ࣮ߦͰ͖ΔΈ • grails-app/init/BootStrap.groovyʹ࣮͢Δ • init: ॳظԽ࣌ͷॲཧ •
destroy: ऴྃ࣌ͷॲཧ • ڥ͝ͱͷઃఆΛهड़Ͱ͖Δ
ڥ • ࣮ߦڥʹԠͯ͡ઃఆɺ෦ͷॲཧΛΓସ͑ΔͨΊͷΈ • ઃఆϑΝΠϧBootstrapͳͲͰσϑΥϧτͰར༻Ͱ͖Δ • σϑΥϧτͰ༻ҙ͞Ε͍ͯΔڥҎԼͷ3ͭ • development •
run-appίϚϯυͰىಈͨ͠ͱ͖ͷڥ • ͘͘͞͞ͱ։ൃͰ͖ΔΑ͏ʹɺࣗಈϦϩʔυΩϟογϡͷແޮԽ͞ΕΔ • test • test-appͳͲͰςετΛ࣮ߦͨ͠ͱ͖ͷڥ • production • warίϚϯυͳͲͰੜ͞ΕͨϑΝΠϧΛىಈͨ͠ͱ͖ͷڥ
ઃఆϑΝΠϧͰͷ༻ྫ environments: development: dataSource: dbCreate: create-drop url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE test: dataSource:
dbCreate: update url: jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE production: dataSource: dbCreate: update url: jdbc:h2:./prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
Bootstrap.groovyͰͷ༻ྫ class BootStrap { def init = { servletContext
-> environments { development { // 開発時の初期化処理 } test { // テスト時の初期化処理 } production { // 本番環境での初期化処理 } } } ... }
ىಈ࣌ʹςετσʔλΛೖ͢Δ grails-app/init/BootStrap.groovyʹҎԼΛهड़͢Δɻ import todo.Todo class BootStrap { def
init = { servletContext -> environments { development { new Todo(content: "山田さんに電話").save() new Todo(content: "ゴミ袋を買う").save() new Todo(content: "ネコに餌をやる").save() } } } … } https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-bootstrap-groovy
࠶ىಈͯ͠ಈ࡞Λ֬ೝ GrailsͷΠϯλϥΫςΟϒϞʔυ͔ΒҎԼΛ࣮ߦ͢Δɻ grails> stop-app grails> run-app ϒϥβͰdbconsoleΛ։͘ɻ TodoͷσʔλΛ֬ೝ͢Δɻ
ը໘ʹTODOϦετ Λදࣔ͢Δ
ίϯτϩʔϥͷ֓ཁ • ίϯτϩʔϥͱWebϒϥβ͔ΒͷϦΫΤ ετΛड͚ͯɺϨεϙϯεΛฦ͢Ұ࿈ͷॲཧ Λఆٛ͢ΔΫϥε • ϝιου͝ͱʹݸผͷॲཧΛఆٛ͢Δ͜ͱ͕ Ͱ͖ɺ͜ͷϝιουΛΞΫγϣϯͱݺͿ
ίϯτϩʔϥͱΞΫγϣϯͷఆٛྫ class BookController { def index() { ... } def
list() { ... } def show() { ... } }
ΞΫγϣϯͷத͔ΒϨεϙϯεΛฦ͢ • renderϝιουΛ͏ • ༷ʑͳλΠϓͷϨεϙϯεΛฦ͢͜ͱ͕Ͱ͖Δ • ςΩετ • ҙͷϏϡʔΛࢦఆͯ͠ը໘Λදࣔͨ͠Γ •
JSONΛฦ٫ • etc • respondϝιουΛ͏ • AcceptURLͷ֦ுࢠͳͲΛͬͯσʔλΛదͳϑΥʔϚοτͰϨϯμϦϯάͯ͘͠ΕΔ • http://grails.github.io/grails-doc/latest/ref/Controllers/respond.html • ΞΫγϣϯͰϨεϙϯεΛࢦఆ͠ͳ͔ͬͨ߹ίϯτϩʔϥ໊ɺΞΫγϣϯ໊͔ΒࣗಈతʹϏϡʔ͕બ͞ ΕΔ • ྫ: BookControllerͷshowΞΫγϣϯͷ߹ɺgrails-app/views/book/show.gsp͕Ϗϡʔ͕༻͞ΕΔ
renderϝιουͷ༻ྫ class BookController { def action1() { // 何も指定しない //
grails-app/views/book/action1.gspが使われる } def action2() { // grails-app/views/book/display.gspが使われる render(view: 'display') } def action3() { // grails-app/views/shared/display.gspが使われる render(view: '/shared/display') } def action4() { // 文字列を表示 render 'Hello World!' } def action5() { // Bookの一覧をJSONで表示 render Book.list() as JSON // grails.converters.JSON } }
ΞΫγϣϯ͔ΒϏϡʔʹΛ͢ • ͍͔ͭ͘Γํ͕͋Δ͕جຊతͳํ๏ҎԼ ͷ̎ͭ • ΞΫγϣϯ͔ΒMapͷΠϯελϯεΛฦ͢ • renderͷҾͰmodelΛࢦఆ͢Δ
modelͷࢦఆྫ // アクションからMapのインスタンスを返す def show() { [message: 'hello'] }
// renderの引数でmodelを指定する def show() { render(view:'display', model: [message: 'hello']) }
Ϗϡʔ(GSP) • GrailsͰϏϡʔͷ࣮ͱͯ͠GSP(Groovy Server Pages)Λ͏ • ؆୯ʹݴ͏ͱJSP(JavaServer Page)ͷGroovy൛ • ${expr}ͱ͍ͬͨܗͰGSPͷதͰGroovyͷࣜΛॻ͚Δ
• σϑΥϧτͰ༻ҙ͞ΕͨλάϥΠϒϥϦ͕͑Δ • λάϥΠϒϥϦͷҰཡ http://grails.github.io/grails- doc/latest/ ͷӈʹදࣔ͞Ε͍ͯΔTagsΛࢀর
GSPͷྫ <!DOCTYPE html> <html> <head> <meta name="layout" content="main"/> <title>Render Domain</title>
</head> <body> <table> <tr> <td>Name</td> <td>Age</td> </tr> <g:each in="${list}" var="person"> <tr> <td>${person.lastName}, ${person.firstName}</td> <td>${person.age}</td> </tr> </g:each> </table> </body> </html>
ίϯτϩʔϥΛੜ͢Δ GrailsͷΠϯλϥΫςΟϒϞʔυ͔ΒҎԼΛ࣮ߦ͢Δɻ grails> create-controller todo ҎԼͷϑΝΠϧ͕ੜ͞ΕΔɻ grails-app/controllers/todo/TodoController.groovy src/test/groovy/todo/TodoControllerSpec.groovy
ίϯτϩʔϥΛ࣮͢Δ IDEAͰgrails-app/controllers/todo/TodoController.groovyΛ։͍ͯҎԼΛ هड़͢Δɻ package todo class TodoController {
def index() { [todos: Todo.list()] } } https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-todocontroller1-groovy
ϏϡʔΛ࣮͢Δ IDEAͰgrails-app/views/todoΛӈΫϦοΫͯ͠[New]-[File]͔Βindex.gsp ͱ͍͏ϑΝΠϧΛ࡞͢Δɻ ҎԼͷ༰Λهड़͢Δɻ <!doctype html> <html> <head> <meta charset="UTF-8">
<title>MYTODO</title> </head> <body> <h2>TODOリスト</h2> <ul> <g:each in="${todos}" var="todo"> <li>${todo.content}</li> </g:each> </ul> </body> </html> https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-index1-gsp
TODOΛՃ/আ Ͱ͖ΔΑ͏ʹ͢Δ
ը໘͔ΒύϥϝʔλΛड͚औΔ • جຊతͳΓํҎԼ • ҉ͷมparamsͷ༻ • ϦΫΤετύϥϝʔλΛΞΫγϣϯͷҾʹࢦఆ͢Δ • ίϚϯυΦϒδΣΫτΛΞΫγϣϯͷҾʹࢦఆ͢Δ •
υϝΠϯΫϥεΛίϚϯυΦϒδΣΫτͱͯ͠͏͜ͱͰ͖Δ • υϝΠϯΫϥεΛࢦఆ͞Ε͍ͯΔɺ͔ͭϦΫΤετύϥϝʔλͷ தʹidͷύϥϝʔλ͕͋ΕࣗಈతʹͦͷidʹରԠ͢ΔσʔλΛऔ ಘͯ͘͠ΕΔ
paramsͷྫ def save() { new Person(name: params.name, age: params.int('age')).save() new
Person(params).save() } def show() { [person: Person.get(params.id)] }
ϦΫΤετύϥϝʔλΛΞΫγϣ ϯͷҾʹ͢Δྫ def save(String name, Integer age) { new Person(name,
age).save() } def show(Long id) { [person: Person.get(id)] }
υϝΠϯΫϥεΛΞΫγϣϯͷ Ҿʹ͢Δྫ def save(Person person) { person.save() } def
show(Person person) { [person: person] }
save/deleteΞΫγϣϯΛ࣮͢Δ grails-app/controllers/todo/TodoController.groovyʹsaveΞΫγϣϯͱ deleteΞΫγϣϯΛՃ͢Δɻ package todo class TodoController { …
def save(Todo todo) { todo.save(flush: true) redirect action: 'index' } def delete(Todo todo) { todo.delete(flush: true) redirect action: 'index' } } https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-todocontroller2-groovy
Ճ/আͷը໘Λ࣮͢Δ grails-app/views/todo/index.gspʹҎԼΛՃ͢Δɻ … <body> <g:form action="save"> <g:textField name="content" /> <g:submitButton
name="create" value="作成" /> </g:form> <h2>TODOリスト</h2> <ul> <g:each in="${todos}" var="todo"> <li> ${todo.content} <g:form action="delete" id="${todo.id}"> <g:submitButton name="delete" value="削除" /> </g:form> </li> </g:each> </ul> … https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-index2-gsp
ը໘ͰೖྗνΣοΫΛͰ͖ΔΑ ͏ʹ͢Δ
੍ • υϝΠϯΫϥεͷconstraintsϒϩοΫʹఆٛ͢Δ • nullable:false͚ͩσϑΥϧτͰઃఆ͞ΕΔ • ੍ҎԼͷ3ͭͰओʹར༻͞ΕΔ • όϦσʔγϣϯ •
੍ͷϝΠϯͷ༻్ • Grails͕ߦ͏ೖྗͷόϦσʔγϣϯʹΘΕΔ • σʔλϕʔεͷεΩʔϚ • dbCreate͕createͳͲͷ߹ʹHibernateʹΑͬͯࣗಈੜ͞ΕΔσʔλϕʔεʹ͓͍ͯɺΧϥϜͷܕα ΠζɺNOT NULLͳͲͷ੍ʹΘΕΔ • εΩϟϑΥϧυ • ࣗಈੜ͞ΕΔϏϡʔͷϑΥʔϜཁૉͷछྨଐੑʹΘΕΔ
੍ͷྫ static constraints = { age nullable:false username blank: false
type inList: ["Commercial", "Personal"] username unique:true username matches: /[a-zA-Z]/ password validator: { value, self -> ... } children minSize:5, maxSize:25 age min:0, max:120 mailAddress email: true webSite url: true username notEqual:'root' age range:0..120 price scale:2 children size:5..25 cardNumber creditCard: true }
όϦσʔγϣϯ • ੍ͷఆٛΛجʹೖྗνΣοΫΛ࣮ࢪ͢Δ • όϦσʔγϣϯؔ࿈ͷϝιουυϝΠϯΫϥεʹGrails͕ࣗಈతʹՃ͢Δ • ໌ࣔతͳݺͼग़͠ • domainInstance.validate() •
҉తͳݺͼग़͠ • domainInstance.save()ͷ࣮ߦͨ͠߹ • ίϯτϩʔϥͷΞΫγϣϯͷҾʹυϝΠϯΫϥεΛࢦఆͨ͠߹ • όϦσʔγϣϯ࣮ࢪޙɺΤϥʔ͕͋Δ߹domainInstance.hasErrors()͕trueΛฦ͢
όϦσʔγϣϯͷ༻ྫ // 明示的なバリデーションの実行 def save() { def book = new
Book(params) book.validate() // 明示的に実行 if (book.hasErrors()) { ... } ... } // saveを実行した場合 def save() { def book = new Book(params).save() // 暗黙的にバリデーションを実行ずみ if (book.hasErrors()) { ... } ... } // コントローラのアクションの引数にドメインクラスを指定した場合 def save(Book book) { // 暗黙的にバリデーションを実行ずみ if (book.hasErrors()) { ... } ... }
όϦσʔγϣϯΤϥʔͷ߹ೖ ྗը໘Λදࣔ͢Δ grails-app/controllers/todo/TodoController.groovyʹҎԼΛՃ͢Δɻ class TodoController { … def save(Todo todo)
{ if (todo.hasErrors()) { render view: 'index', model: [todo: todo, todos: Todo.list()] return } todo.save(flush: true) redirect action: 'index' } … } https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-todocontroller3-groovy
ೖྗΤϥʔΛදࣔ͢Δ … <body> <g:hasErrors bean="${todo}"> <g:renderErrors bean="${todo}" /> </g:hasErrors> <g:form
action="save"> <g:textField name="content" value="${task?.content}" /> <g:submitButton name="create" value="作成" /> </g:form> <h2>TODOリスト</h2> … grails-app/views/todo/index.gspΛҎԼͷΑ͏ʹมߋ͢Δɻ https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-index3-gsp
ϝοηʔδΛΧελϚΠζ͢Δ
i18n (internationalization; ࠃࡍԽ) • ༷ʑͳݴޠͷϝοηʔδΛूཧ͠ɺWebϒϥβͷݴޠ ઃఆαʔό্ͷݴޠઃఆͳͲΛجʹɺదͳݴޠͷϝοηʔ δΛద༻͢ΔΈ • grails-app/i18nσΟϨΫτϦԼʹϩέʔϧ͝ͱͷϓϩύςΟ ϑΝΠϧͰཧ͢Δ
• λάϦϒͷg:messageΛͬͯϝοηʔδΛࢀরͰ͖Δ • όϦσʔγϣϯͷσϑΥϧτϝοηʔδͷఆٛʹΘ͍ͯΔ
ϝοηʔδϑΝΠϧͷྫ my.sample.message.hello = こんにちは、i18n。 my.sample.message.withArgs = 引数1番目:{0}, 2番目:{1}, 3番目:{2}
ϝοηʔδͷࢀরྫ // コントローラで使う def show() { println message(code: 'my.sample.message.hello') println
message(code: 'my.sample.message.withArgs', args: ['Grails', 'Groovy', 'Advocate']) ... } // GSPの内でタグライブラリを使う <g:message code="my.sample.message.hello" /> <g:message code="my.sample.message.withArgs" args="${ ['Grails', 'Groovy', 'Advocate'] }" /> // GSPの評価式の中で使う ${message(code: 'my.sample.message.hello')} ${message(code: 'my.sample.message.hello', args=['Grails', 'Groovy', 'Advocate'])}
όϦσʔγϣϯͷϝοηʔδΛม ߋ͢Δ grails-app/i18n/messages_ja.propertiesͰҎԼͷΑ͏ʹϝοηʔδΛมߋ ͢Δɻ default.invalid.max.size.message={1}の{0}は{3}文字以内で入力してください。 default.null.message={1}の{0}が入力されていません。 todo.label=TODO todo.content.label=内容 ಉ͡ϑΝΠϧʹҎԼͷϝοηʔδΛՃ͢Δɻ https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-messages1-properties
ը໘ͷϝοηʔδΛఆٛ͢Δ app.name=MYTODO grails-app/i18n/messages_ja.propertiesʹҎԼͷϝοηʔδΛՃ͢Δɻ https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-messages2-properties
ϝοηʔδΛࢀর͢Δ … <head> <meta charset="UTF-8"> <title><g:message code="app.name" /></title> </head> <body>
… <g:form action="save"> <g:textField name="content" value="${task?.content}" /> <g:submitButton name="create" value="${message(code: 'default.button.create.label')}" /> </g:form> <h2><g:message code="default.list.label" args="${[message(code: 'todo.label')]}"/></h2> <ul> <g:each in="${todos}" var="todo"> <li> ${todo.content} <g:form action="delete" id="${todo.id}"> <g:submitButton name="delete" value="${message(code: 'default.button.delete.label')}" /> </g:form> … grails-app/views/todo/index.gspΛҎԼͷΑ͏ʹมߋ͢Δɻ https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-index4-gsp
TODOͷߜΓࠐΈػೳΛ࣮͢Δ
ΫΤϦ • GrailsͰɺσʔλϕʔεʹݕࡧΫΤϦΛൃߦ͢ΔͨΊͷ༷ʑͳํ๏͕ఏڙ͞ Ε͍ͯΔ • μΠφϛοΫϑΝΠϯμ • ΫϥΠςϦΞ • whereΫΤϦ
• HQL (Hibernate Query Language) • ໊લ͖ΫΤϦ • ωΠςΟϒSQL
μΠφϛοΫϑΝΠϯμ • ໋໊نଇʹԊͬͨϝιουݺͼग़͠Λ͢ΔͱࣗಈతʹΫΤϦΛൃߦͯ͘͠ΕΔػೳ • Ұ൪ੲ͔Β͋ΔΫΤϦͷػೳ • جຊύλʔϯҎԼͷ3छ • findBy* •
ࢦఆ͞Εͨ݅ʹҰக͢Δ࠷ॳͷ1݅Λฦ͢ • findAllBy* • ࢦఆ͞Εͨ݅ʹҰக͢Δͯ͢Λฦ͢ • countBy* • ࢦఆ͞Εͨ݅ʹҰக͢ΔϨίʔυ݅Λฦ͢ • ϝιου໊ͷʮ*ʯͷ෦ʹݕࡧ݅Λࢦఆ͢Δ • ରԠ͢Δϝιου͕͋Β͔͡Ίఆٛ͞Ε͍ͯΔ༁Ͱͳ͍ • ࢦఆ͞Εͨϝιου໊͔Βಈతʹݕࡧ݅Λஅͯ͠ΫΤϦΛ࣮ߦ͢Δ
μΠφϛοΫϑΝΠϯμͷྫ Book.findAllByTitleAndAuthor("The Hoge", "Mike Davis") Book.findAllByReleaseDateBetween(firstDate, new Date()) Book.findAllByReleaseDateGreaterThanEquals(firstDate) Book.findAllByTitleLike("%Hobbit%")
Book.findAllByTitleIlike("%Hobbit%") // ignore case Book.findAllByTitleNotEqual("Harry Potter") Book.findAllByReleaseDateIsNull() Book.findAllByReleaseDateIsNotNull() Book.findAllPaperbackByAuthor("Douglas Adams") Book.findAllNotPaperbackByAuthor("Douglas Adams") Book.findAllByAuthorInList(["Douglas Adams", "Hunter S. Thompson"])
ΫϥΠςϦΞ • HibernateͷCriteria APIΛϥοϓͨ͠GroovyͷDSL Ͱɺෳࡶͳݕࡧ݅ΛߏஙͰ͖Δ • ରυϝΠϯΫϥεʹର͢ΔCriteriaΦϒδΣΫτΛੜ ͯ͠ɺͦ͜ʹ݅Λ༩͍ͯ͘͠ • ੜʹDomainClass.createCriteria·ͨ
DomainClass.withCriteriaϝιουΛ͏ • ifจͳͲͷGroovyίʔυ͕ී௨ʹ͑ΔͨΊɺಛఆͷ ߹ͷΈ༗ޮͳ݅Λࢦఆ͢Δ͜ͱ؆୯
ΫϥΠςϦΞͷྫ def c = Book.createCriteria() def results = c.list {
def now = new Date() between('releaseDate', now - 7, now) like('title', '%Groovy%') } def results = Book.withCriteria { def now = new Date() between('releaseDate', now - 7, now) // 管理者以外の場合は公開された本だけに限定する if (!person.isAdmin()) { eq('available', true) } like('title', '%Groovy%') }
WhereΫΤϦ • GroovyͷASTมΛ׆༻ͨ͠ΫΤϦͷ Έ • ίϯύΠϧ࣌ʹ੩తʹܕνΣοΫ͕Մೳ • ݅ࣜΛॻ͘Α͏ʹΫΤϦͷ݅Λهड़ Ͱ͖Δ •
Ұ൪৽͍͠
WhereΫΤϦͷྫ Person.where { name == "Bart" }.list() Person.where { (name
== "Bart") && (age == 35) }.list() Person.where { (name == "Bart") || (age > 18) }.list() Person.where { age in 18..65 }.list() Person.where { name ==~ /%yamada%/ }.list()
Ωʔϫʔυ͕ࢦఆ͞Εͨ߹Ωʔ ϫʔυͰݕࡧ͢Δ grails-app/controllers/todo/TodoController.groovyʹҎԼΛՃ͢Δɻ class TodoController { def index(String keyword)
{ if (keyword) { return [todos: Todo.where { content ==~ /%$keyword%/ }.list()] } [todos: Todo.list()] } … } https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-todocontroller4-groovy
ΩʔϫʔυΛೖྗͰ͖ΔΑ͏ʹ͢Δ … <h2><g:message code="default.list.label" args="${[message(code: 'todo.label')]}"/></h2> <g:form action="index"> <g:textField
name="keyword" value="${params.keyword}"/> <g:submitButton name="filter" value="絞り込み"/> </g:form> <ul> <g:each in="${todos}" var="todo"> <li> ${todo.content} <g:form action="delete" id="${todo.id}"> <g:submitButton name="delete" value="${message(code: 'default.button.delete.label')}" /> </g:form> </li> </g:each> </ul> … grails-app/views/todo/index.gspΛҎԼͷΑ͏ʹมߋ͢Δɻ https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-index5-gsp
ڞ௨ͷϨΠΞτΛఆٛ͢Δ
SiteMesh • GrailsͰSiteMeshΛͬͯɺϏϡʔͷϨΠΞτ Λߦ͍ͬͯΔ • ༻ྫ: ϔομϑολɺαΠυόʔͳͲΛఆٛ͠ ͨϨΠΞτϑΝΠϧΛఆٛ͢Δ • ϨΠΞτgrails-app/views/layoutsσΟϨΫτϦ
ʹஔ͢Δ • ϨΠΞτϑΝΠϧΛ͏ଆhtmlͷmetaλάΛ ͬͯlayoutsσΟϨΫτϦͷϑΝΠϧ໊Λࢦఆ͢Δ
ϨΠΞτϑΝΠϧͷఆٛྫ <!doctype html> <html> <head> <title><g:layoutTitle default="Grails"/></title> <asset:stylesheet src="application.css"/> <asset:javascript
src="application.js"/> <g:layoutHead/> </head> <body> <header>My App</header> <g:layoutBody/> <footer>Copyright 2015</footer> </body> </html>
ϨΠΞτϑΝΠϧͷࢦఆྫ <!doctype html> <html> <head> <meta name="layout" content="main" /> <title>My
Page</title> </head> <body> <p>Hello Grails!</p> </body> </html>
ϨΠΞτϑΝΠϧΛఆٛ͢Δ grails-app/views/layouts/main.gspΛҎԼͷΑ͏ʹมߋ͢Δɻ <!doctype html> <html> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible"
content="IE=edge"> <title><g:layoutTitle default="${message(code: 'app.name')}"/></title> <g:layoutHead/> </head> <body> <h1><g:message code="app.name" /></h1> <g:layoutBody/> </body> </html> https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-main1-gsp
ϨΠΞτϑΝΠϧΛࢦఆ͢Δ <!doctype html> <html> <head> <meta name="layout" content="main"/> <title><g:message code="app.name"
/></title> </head> … grails-app/views/todo/index.gspΛҎԼͷΑ͏ʹมߋ͢Δɻ https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-index6-gsp
URLϚοϐϯάΛΧελϚΠζ ͢Δ
URLϚοϐϯά • URLϚοϐϯάΛΧελϚΠζ͢Δ͜ ͱͰɺURLͱίϯτϩʔϥ/ΞΫγϣ ϯ/ϏϡʔͷؔΛࣗ༝ʹઃఆͰ͖Δ • grails-app/controllers/ UrlMappings.groovyʹఆٛ͢Δ
URLϚοϐϯάͷྫ class UrlMappings { static mappings = { //
デフォルトのマッピング設定 "/$controller/$action?/$id?(.$format)?"{ constraints { // apply constraints here } } // controllerとactionを指定 "/product"(controller: "product", action: "list") "/product"(controller: "product") // viewを指定した設定 "/"(view:"/index") // ステータスコードを指定した設定 "500"(controller: "errors", exception: MyException) "500"(view: "/errors/serverError", exception: MyAnotherException) "500"(view:'/error') "404"(view:'/notFound') } }
ϗʔϜը໘ΛTODOʹ͢Δ grails-app/controllers/UrlMappings.groovyΛҎԼͷΑ͏ʹมߋ͢Δɻ class UrlMappings { static mappings = {
"/$controller/$action?/$id?(.$format)?"{ constraints { // apply constraints here } } "/"(controller: 'todo') "500"(view:'/error') "404"(view:'/notFound') } } https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-urlmappings-groovy
੩తϦιʔεΛ͏
Asset Pipeline ϓϥάΠϯ • JavascriptɺελΠϧγʔτɺը૾ͱ͍ͬͨ੩తϦιʔεΛཧ͢ ΔϓϥάΠϯ • Rails͔ΒͷΠϯεύΠΞ͞Ε։ൃ͞Εͨ • ҎԼͷػೳΛఏڙ͢Δ
• Ξηοτͷ݁߹ɺ࠷খԽɺѹॖ • ϑΟϯΨʔϓϦϯτ • CoffeeScriptSASSͱ͍ͬͨΞηοτͷίϯύΠϧ • Ξηοτgrails-appԼͷassets/javascriptsɺassets/ stylesheetsɺassets/imagesσΟϨΫτϦʹ֨ೲ͢Δ
ϚχϑΣετͱσΟϨΫςΟϒ • Asset PipelineΛ͏ʹϚχϑΣετϑΝΠϧΛఆٛͯ͠ɺͦΕΛϏϡʔ͔ΒಡΈࠐΉ • ϚχϑΣετෳͷϦιʔεΛవΊΔͨΊͷఆٛ • ΤϯτϦʔϙΠϯτͷϑΝΠϧͷΑ͏ͳͷ • σΟϨΫςΟϒͦͷϚχϡϑΣετͷதͰϦιʔεΛࢦఆ͢ΔͨΊͷه๏
• ϚχϑΣετͷதͰίϝϯτͱͯ͠هड़͢Δ • Α͘͏σΟϨΫςΟϒҎԼͷ3ͭ • require • ࢦఆ͞ΕͨϦιʔεΛಡΈࠐΉ • require_tree • ࢦఆ͞ΕͨύεͷϦιʔεΛ࠶ؼతʹಡΈࠐΉ • require_self • ࣗͷϦιʔεΛಡΈࠐΉ
CSSͷϚχϑΣετͷྫ /* *= require jquery *= require main *= require_self
*/ body { color: red; }
Ϗϡʔ͔ΒͷಡΈࠐΈྫ <!doctype html> <html> <head> <title><g:layoutTitle default="Grails" /></title> <asset:stylesheet src="application.css"
/> <asset:javascript src="application.js" /> <g:layoutHead/> </head> <body> <header>My App</header> <g:layoutBody/> <footer>Copyright 2015</footer> </body> </html>
JavaScriptɺCSSͷϥΠϒϥϦΛ Asset PipelineͰ͏ • ҎԼͷ3ͭͷํ๏͕͋Δ • ϑΝΠϧΛμϯϩʔυͯ͠grails-app/assets σΟϨΫτϦʹखಈͰల։͢Δ • GrailsͷϓϥάΠϯΛ͏
• WebJarsΛ͏ • http://www.webjars.org/
WebJarsΛͬͯTwitter BootstrapͷϦιʔεΛऔಘ͢Δ build.gradleʹҎԼΛՃ͢Δɻ … dependencies { … console "org.grails:grails-console"
provided "org.webjars.bower:bootstrap:3.3.5" } … https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-build-gradle
ϚχϑΣετΛఆٛ͢Δ grails-app/assets/stylesheets/application.cssΛҎԼͷΑ͏ʹมߋ͢Δɻ /* *= require webjars/bootstrap/3.3.5/dist/css/bootstrap *= require_self */ https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-application-css
ϨΠΞτϑΝΠϧ͔ΒϦιʔε ΛಡΈࠐΉ grails-app/views/layouts/main.gspΛҎԼͷΑ͏ʹมߋ͢Δɻ <!doctype html> <html> <head> <meta charset="UTF-8"> <meta
http-equiv="X-UA-Compatible" content="IE=edge"> <title><g:layoutTitle default="${message(code: 'app.name')}" /></title> <asset:stylesheet src="application.css"/> <asset:javascript src="application.js"/> <g:layoutHead/> </head> <body> <h1><g:message code="app.name" /></h1> <g:layoutBody/> </body> </html> https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-main2-gsp
Twitter BootstrapͷελΠϧద༻ ͢Δ https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-index7-gsp grails-app/views/todo/index.gspΛҎԼͷΑ͏ʹมߋ͢Δɻ grails-app/views/layouts/main.gspΛҎԼͷΑ͏ʹมߋ͢Δɻ https://gist.github.com/yamkazu/08e5daed0092a24e205e#file-main3-gsp
͕࣌ؒ͋·ͬͨ߹ͷΞυϦϒωλީิ • ςετ • αʔϏε • Πϯλʔηϓλʔ • ϩά •
Ϗϧυ • ϓϥάΠϯ
ࢀߟใ
Grails http://grails.github.io/grails-doc/latest/ ຊՈϦϑΝϨϯε http://grails.jp/doc/latest/ ຊޠ༁
Groovy http://gihyo.jp/book/2011/978-4-7741-4727-7 ϓϩάϥϛϯάGROOVY
Spring Boot http://www.amazon.co.jp/dp/4777518655 ͡ΊͯͷSpring Boot http://projects.spring.io/spring-boot/ ຊՈϦϑΝϨϯε
Gradle http://www.amazon.co.jp/dp/4798136433/ Gradleపఈೖ http://gradle.monochromeroad.com/docs/ ϦϑΝϨϯεͷ༁
Q & A