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
410
Grails 3でWeb APIを簡単に作ろう!
yamkazu
6
2.1k
Other Decks in Programming
See All in Programming
個人アプリを2年ぶりにアプデしたから褒めて / I just updated my personal app, praise me!
lovee
0
300
Оптимизируем производительность блока Казначейство
lamodatech
0
980
Flatt Security XSS Challenge 解答・解説
flatt_security
0
800
“あなた” の開発を支援する AI エージェント Bedrock Engineer / introducing-bedrock-engineer
gawa
9
1.2k
非ブラウザランタイムとWeb標準 / Non-Browser Runtimes and Web Standards
petamoriken
0
440
オニオンアーキテクチャを使って、 Unityと.NETでコードを共有する
soi013
0
380
Beyond ORM
77web
11
1.6k
Azure AI Foundryのご紹介
qt_luigi
1
250
[Fin-JAWS 第38回 ~re:Invent 2024 金融re:Cap~]FaultInjectionServiceアップデート@pre:Invent2024
shintaro_fukatsu
0
320
ファインディの テックブログ爆誕までの軌跡
starfish719
1
690
functionalなアプローチで動的要素を排除する
ryopeko
1
800
いりゃあせ、PHPカンファレンス名古屋2025 / Welcome to PHP Conference Nagoya 2025
ttskch
1
240
Featured
See All Featured
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
28
9.2k
The Art of Programming - Codeland 2020
erikaheidi
53
13k
The Cult of Friendly URLs
andyhume
78
6.2k
Designing on Purpose - Digital PM Summit 2013
jponch
117
7.1k
Fashionably flexible responsive web design (full day workshop)
malarkey
406
66k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
33
2.8k
We Have a Design System, Now What?
morganepeng
51
7.4k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
226
22k
Embracing the Ebb and Flow
colly
84
4.5k
Building an army of robots
kneath
302
45k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
3
370
Bootstrapping a Software Product
garrettdimon
PRO
305
110k
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